From: Chris Koeritz Date: Wed, 25 Jan 2012 07:24:09 +0000 (-0500) Subject: wow. that was easy: git mv core nucleus X-Git-Tag: 2.140.90~1656^2~22 X-Git-Url: https://feistymeow.org/gitweb/?a=commitdiff_plain;h=457b128b77b5b4a0b7dd3094de543de2ce1477ad;p=feisty_meow.git wow. that was easy: git mv core nucleus --- diff --git a/core/.cproject b/core/.cproject deleted file mode 100644 index 13aecb09..00000000 --- a/core/.cproject +++ /dev/null @@ -1,972 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -make - -all -true -true -true - - -make - -all -true -true -false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -make - -all -true -true -true - - -make - -all -true -true -false - - - - - - - - - diff --git a/core/.project b/core/.project deleted file mode 100644 index 6057985b..00000000 --- a/core/.project +++ /dev/null @@ -1,82 +0,0 @@ - - - hoople_core - - - - - - org.eclipse.cdt.managedbuilder.core.genmakebuilder - clean,full,incremental, - - - ?name? - - - - org.eclipse.cdt.make.core.append_environment - true - - - org.eclipse.cdt.make.core.autoBuildTarget - all - - - org.eclipse.cdt.make.core.buildArguments - - - - org.eclipse.cdt.make.core.buildCommand - make - - - org.eclipse.cdt.make.core.buildLocation - ${workspace_loc:/hoople_core/Debug} - - - org.eclipse.cdt.make.core.cleanBuildTarget - clean - - - org.eclipse.cdt.make.core.contents - org.eclipse.cdt.make.core.activeConfigSettings - - - org.eclipse.cdt.make.core.enableAutoBuild - false - - - org.eclipse.cdt.make.core.enableCleanBuild - true - - - org.eclipse.cdt.make.core.enableFullBuild - true - - - org.eclipse.cdt.make.core.fullBuildTarget - all - - - org.eclipse.cdt.make.core.stopOnError - true - - - org.eclipse.cdt.make.core.useDefaultBuildCmd - true - - - - - org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder - - - - - - org.eclipse.cdt.core.cnature - org.eclipse.cdt.core.ccnature - org.eclipse.cdt.managedbuilder.core.managedBuildNature - org.eclipse.cdt.managedbuilder.core.ScannerConfigNature - - diff --git a/core/applications/bookmark_tools/bookmark_tree.cpp b/core/applications/bookmark_tools/bookmark_tree.cpp deleted file mode 100644 index c8fc1f55..00000000 --- a/core/applications/bookmark_tools/bookmark_tree.cpp +++ /dev/null @@ -1,485 +0,0 @@ -/*****************************************************************************\ -* * -* Name : bookmark_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -///#include //temp - -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace nodes; -using namespace structures; -using namespace textual; - -#define DEBUG_MARKS - // uncomment to have more debugging noise, but a reasonable amount. -//#define DEBUG_MARKS_TREE - // uncomment to get crazy noisy debug noise about tree traversal. - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s) -#define SHOW_LINE a_sprintf("line %d: ", _line_number) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), SHOW_LINE + s) -#define DEADLY_LINE (astring(func) + a_sprintf(", line %d: ", _line_number)) - -const int ESTIMATED_ELEMENTS = 100; - // we're planning for about this many links to be efficiently handled. - -const int MAX_LINE_SIZE = 256 * KILOBYTE; - // the largest line we'll process in the links database. - -// used to compare two strings while ignoring case; we use this to find -// our categories in the symbol table. -bool case_insense_compare(const astring &a, const astring &b) -{ return a.iequals(b); } - -//////////////////////////////////////////////////////////////////////////// - -listo_links::listo_links() : amorph(), _next_index(0) {} - -void listo_links::add(link_record *new_rec, bool sort) -{ - // we don't sort blank lines--they just get dropped in the end of - // the section. - if (sort && new_rec->_description.t()) { - for (int i = _next_index; i < elements(); i++) { - const astring &desc_cur = borrow(i)->_description; -//this check doesn't make much sense; it only checks if the description is equal? -// if it were really sorting, wouldn't it need to check if the check is greater than current? - if (desc_cur.iequals(new_rec->_description) -// || shouldn't there be a case for this being greater than the current??? - || !desc_cur) { - insert(i + 1, 1); - put(i + 1, new_rec); - return; - } - } - } - append(new_rec); - if (!sort) - _next_index = elements(); -} - -//////////////////////////////////////////////////////////////////////////// - -class symbol_int : public symbol_table -{ -public: - symbol_int() : symbol_table(10) {} -}; - -//////////////////////////////////////////////////////////////////////////// - -bookmark_tree::bookmark_tree() -: _line_number(0), - _mark_tree(new inner_mark_tree("Root", 0, ESTIMATED_ELEMENTS)), - _link_count(0), - _category_count(0), - _last_parent(_mark_tree), - _last_node(_mark_tree), - _links_seen(new symbol_int), - _category_names(new string_table) -{} - -bookmark_tree::~bookmark_tree() -{ - WHACK(_links_seen); - WHACK(_mark_tree); - WHACK(_category_names); -} - -void bookmark_tree::break_name(const astring &to_break, astring &name, - astring &nick) -{ - nick = astring::empty_string(); - name = to_break; - int indy = name.find('['); - if (negative(indy)) return; - nick = name.substring(indy + 1, name.end()); - while ( (nick[nick.end()] == ' ') || (nick[nick.end()] == ']') ) - nick.zap(nick.end(), nick.end()); - name.zap(indy, name.end()); - name.strip_spaces(); - nick.strip_spaces(); -} - -inner_mark_tree &bookmark_tree::access_root() { return *_mark_tree; } - -bool bookmark_tree::magic_category_comparison(const astring &a, const astring &b) -{ -// FUNCDEF("magic_category_comparison"); -//LOG(astring("compare: a=") + a + " b=" + b); - if (a.iequals(b)) return true; - astring a_name, a_nick; - break_name(a, a_name, a_nick); - astring b_name, b_nick; - break_name(b, b_name, b_nick); - if (a_name.iequals(b_name)) return true; - if (a_nick.t() && a_nick.iequals(b_name)) return true; - if (b_nick.t() && a_name.iequals(b_nick)) return true; - if (a_nick.t() && b_nick.t() && a_nick.iequals(b_nick)) return true; - return false; -} - -const astring &HTTP_HEAD = "http://"; -const astring &FTP_HEAD = "ftp://"; -const astring &WWW_SITE = "www."; -const astring &FTP_SITE = "ftp."; - -bool bookmark_tree::advance(int &index, const astring &check, const astring &finding) -{ - if (check.compare(finding, index, 0, finding.length(), false)) { - index += finding.length(); - return true; - } - return false; -} - -int bookmark_tree::find_prune_point(const astring &to_prune) -{ - int to_return = 0; - advance(to_return, to_prune, HTTP_HEAD); - advance(to_return, to_prune, FTP_HEAD); - advance(to_return, to_prune, WWW_SITE); - advance(to_return, to_prune, FTP_SITE); - return to_return; -} - -astring bookmark_tree::prune_link_down(const astring &to_prune) -{ -//printf("%s\n", (astring("pruned=") + to_prune.substring(find_prune_point(to_prune), to_prune.end())).s()); - return to_prune.substring(find_prune_point(to_prune), to_prune.end()); } - -bool bookmark_tree::excellent_link_comparator(const astring &a, const astring &b) -{ - int prune_a = find_prune_point(a); - int prune_b = find_prune_point(b); - int bigger_len = maximum(a.length() - prune_a, b.length() - prune_b); - bool to_return = a.compare(b, prune_a, prune_b, bigger_len, false); -//if (to_return) printf("%s and %s are equal.", a.s(), b.s()); - return to_return; -} - -inner_mark_tree *bookmark_tree::find_parent(const astring &parent_name) -{ - FUNCDEF("find_parent"); - // first, look for the node above the last parent. - inner_mark_tree *parent = dynamic_cast - (_last_parent->find(parent_name, inner_mark_tree::recurse_upward, - magic_category_comparison)); - -#ifdef DEBUG_MARKS_TREE - if (parent) - LOG(astring("trying upwards find for parent node ") + parent_name); -#endif - - if (!parent) { -#ifdef DEBUG_MARKS_TREE - LOG(astring("upwards find failed seeking on last_parent node ") - + parent_name); -#endif - - // we didn't find the parent above the last category... - parent = dynamic_cast(_last_node->find(parent_name, - inner_mark_tree::recurse_upward, magic_category_comparison)); - } - - if (!parent) { -#ifdef DEBUG_MARKS_TREE - LOG(astring("upwards find failed seeking on last_node ") + parent_name); -#endif - - // last node didn't help either. - parent = dynamic_cast(_mark_tree->find(parent_name, - inner_mark_tree::recurse_downward, magic_category_comparison)); - } - if (!parent) { - // failed to find the parent node, so hook it to the root node. - LOG(astring("failed to find parent node ") + parent_name); - - // create a parent node and use it for this guy. - inner_mark_tree *new_node = new inner_mark_tree(parent_name, - _line_number, ESTIMATED_ELEMENTS); - _mark_tree->attach(new_node); - _mark_tree->sort(); - _category_count++; - - parent = new_node; - } else { -#ifdef DEBUG_MARKS_TREE - LOG(astring("found parent node ") + parent_name); -#endif - } - - return parent; -} - -inner_mark_tree *bookmark_tree::process_category(const string_array &items) -{ - FUNCDEF("process_category"); - const astring &category_name = items[1]; - const astring &parent_name = items[2]; - - if (items.length() > 3) { - // complain about a possibly malformed category. - LOG(astring("category ") + category_name + " under " + parent_name - + " has extra fields!"); - } - -//BASE_LOG("CURRENT:"); -//BASE_LOG(_mark_tree->text_form()); - - // make sure we don't add anything to the tree if this is the root. - if (!parent_name || magic_category_comparison("Root", category_name)) { -#ifdef DEBUG_MARKS_TREE - LOG(astring("skipping parent node for ") + category_name); -#endif - return _mark_tree; - } - - // ensure that the categories aren't competing with other names. - astring main_name, nickname; - break_name(category_name, main_name, nickname); - astring *found1 = _category_names->find(main_name, case_insense_compare); - astring *found2 = _category_names->find(nickname, case_insense_compare); - if (found1 || found2) { - astring catnames; - if (found1) catnames = *found1; // add the first match, if it exists. - if (found2) { - if (!!catnames) catnames += " and "; - catnames += *found2; - } - LOG(astring("category name \"") + category_name - + "\" in conflict with existing: " + catnames); - inner_mark_tree *fake_it = NIL; - -//hmmm: neither of these are right; they need to use a comparator that -// uses our magic comparison function. - - if (found1) { -#ifdef DEBUG_MARKS - LOG(astring("found existing category for main name: ") + main_name); -#endif -// fake_it = (inner_mark_tree *)_mark_tree->find(*found1, -// symbol_tree::recurse_downward); - fake_it = dynamic_cast(_mark_tree->find - (*found1, inner_mark_tree::recurse_downward, - magic_category_comparison)); - } - if (fake_it) { -#ifdef DEBUG_MARKS - LOG(astring("returning existing category for main name: ") + main_name - + " as: " + fake_it->name()); -#endif - return fake_it; - } - if (found2) { -#ifdef DEBUG_MARKS - LOG(astring("found existing category for nickname: ") + nickname); -#endif -/// fake_it = (inner_mark_tree *)_mark_tree->find(*found2, -/// symbol_tree::recurse_downward); - fake_it = dynamic_cast(_mark_tree->find - (*found2, inner_mark_tree::recurse_downward, - magic_category_comparison)); - } - if (fake_it) { -#ifdef DEBUG_MARKS - LOG(astring("returning existing category for nickname: ") + nickname - + " as: " + fake_it->name()); -#endif - return fake_it; - } - LOG("==> failure to find a match for either category!"); - deadly_error(class_name(), func, "collision resolution code failed; " - "please fix category error"); - return NIL; - } - // now that we know these names are unique, we'll add them into the list - // so future categories can't reuse these. - _category_names->add(main_name, main_name); - if (!!nickname) _category_names->add(nickname, nickname); - - inner_mark_tree *parent = find_parent(parent_name); - _last_parent = parent; // set the parent for the next time. - - // see if the category is already present under the parent. - for (int i = 0; i < parent->branches(); i++) { - inner_mark_tree *curr = dynamic_cast(parent->branch(i)); - if (!curr) - non_continuable_error(class_name(), DEADLY_LINE, "missing branch in tree"); -#ifdef DEBUG_MARKS_TREE - LOG(astring("looking at branch ") + curr->name()); -#endif - if (magic_category_comparison(curr->name(), category_name)) { - // it already exists? argh. - LOG(astring("category ") + category_name + " already exists under " - + parent_name + "."); - _last_node = curr; - return curr; - } - } - - inner_mark_tree *new_node = new inner_mark_tree(category_name, - _line_number, ESTIMATED_ELEMENTS); - parent->attach(new_node); - parent->sort(); - _last_node = new_node; - - _category_count++; - -#ifdef DEBUG_MARKS_TREE - LOG(astring("attaching node ") + category_name + " to parent " - + parent->name()); -#endif - return new_node; -} - -void bookmark_tree::process_link(const string_array &items) -{ - FUNCDEF("process_link"); - astring description = items[1]; - astring parent_name = items[2]; - astring url = "UNKNOWN"; - if (items.length() >= 4) url = items[3]; - - // strip any directory slashes that are provided as a suffix. we don't need - // them and they tend to confuse the issue when we look for duplicates. - while (url[url.end()] == '/') { - url.zap(url.end(), url.end()); - } - - // make some noise if they seem to have a badly formed link. - if (items.length() < 4) { - LOG(astring("link ") + description + " under " + parent_name - + " has no URL!"); - } else if (items.length() > 4) { - LOG(astring("link ") + description + " under " + parent_name - + " has extra fields!"); - } - - // find the parent for this link. - inner_mark_tree *parent = find_parent(parent_name); - _last_parent = parent; // set the parent for the next time. - - // see if the link already exists. - int *found = _links_seen->find(url, excellent_link_comparator); - if (found) { - // this is not so great; a duplicate link has been found. - LOG(a_sprintf("Duplicate Link: line %d already has ", *found) + url); - return; - } else { - _links_seen->add(prune_link_down(url), _line_number); - } - - // add the link to the parent. - link_record *new_rec = new link_record(description, url, _line_number); - parent->_links.add(new_rec); - - _link_count++; -} - -void bookmark_tree::process_comment(const astring ¤t_line_in) -{ -/// FUNCDEF("process_comment"); - astring current_line = current_line_in; - - // output the comment as simple text. -//BASE_LOG("comment case"); - if (current_line.contains("http:")) { - astring hold_curr = current_line; - int indy = current_line.find("http:"); - hold_curr.zap(0, indy - 1); - current_line = astring("        " - "" + hold_curr + ""; - } else if (current_line.t()) { - // snap the comment character off of the front. - current_line.zap(0, 0); - } - - link_record *new_rec = new link_record(current_line, - astring::empty_string(), _line_number); - _last_node->_links.add(new_rec, false); -} - -int bookmark_tree::read_csv_file(const astring &input_filename) -{ - FUNCDEF("read_csv_file"); - byte_filer input_file(input_filename, "r"); - if (!input_file.good()) - non_continuable_error(class_name(), DEADLY_LINE, - "the input file could not be opened"); - - string_array items; // parsed in csv line. - astring current_line; // read from input file. - - // read the lines in the file, one at a time. - while (input_file.getline(current_line, MAX_LINE_SIZE) > 0) { - _line_number++; // go to the next line. - // remove the carriage returns first. - while (parser_bits::is_eol(current_line[current_line.end()])) { - current_line.zap(current_line.end(), current_line.end()); - } - current_line.strip_spaces(); - if (!current_line.length()) { -// // blank lines get treated as a special case. they are always added -// // at the end of the list. -// process_comment(current_line); - continue; - } else if (current_line[0] == '#') { - // handle a comment in the database. - process_comment(current_line); - } else { - // csv parse the line, since we don't support much else. - bool parsed = list_parsing::parse_csv_line(current_line, items); - if (!parsed) - non_continuable_error(class_name(), DEADLY_LINE, - astring("the line could not be parsed: ") + current_line); - if (!items.length()) { - LOG("bad formatting on this line."); - continue; - } - if (items[0].iequals("C")) { - inner_mark_tree *node = process_category(items); - if (!node) { - LOG(astring("failed to get a node for ") + items[1]); - } - } else if (items[0].iequals("L")) { - process_link(items); - } else { - non_continuable_error(class_name(), DEADLY_LINE, - astring("unknown format in line: ") + current_line); - } - } - } - return 0; -} - diff --git a/core/applications/bookmark_tools/bookmark_tree.h b/core/applications/bookmark_tools/bookmark_tree.h deleted file mode 100644 index 744d9fd3..00000000 --- a/core/applications/bookmark_tools/bookmark_tree.h +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef BOOKMARK_TREE_CLASS -#define BOOKMARK_TREE_CLASS - -/*****************************************************************************\ -* * -* Name : bookmark_tree * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Parses a link database in HOOPLE format into tree structure. * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -// forward. -class inner_mark_tree; -class link_record; -class listo_links; -class symbol_int; - -//////////////////////////////////////////////////////////////////////////// - -class bookmark_tree -{ -public: - bookmark_tree(); - virtual ~bookmark_tree(); - DEFINE_CLASS_NAME("bookmark_tree"); - - int read_csv_file(const basis::astring &input_filename); - // reads the file contents of "input_filename" into this tree. - - static void break_name(const basis::astring &to_break, basis::astring &name, - basis::astring &nick); - // breaks a category name into the two components, if they exist. - - static bool magic_category_comparison(const basis::astring &a, const basis::astring &b); - // compares the two strings "a" and "b" and returns true if either the - // main name or the nickname matches either. - - static basis::astring prune_link_down(const basis::astring &to_prune); - // reduces a URL to its bare bones. it will strip out the "http://" and "www." and such. - - static bool excellent_link_comparator(const basis::astring &a, const basis::astring &b); - // a string comparator that handles how links are often formed. it uses the link pruner - // to decide whether the links are equal at their root. - - inner_mark_tree *process_category(const structures::string_array &items); - // handles category declarations and adds the new category to our list. - // this tries to do the intelligent thing if the category is already - // found to exist, meaning that the file has a duplicate category - // definitions. - - void process_link(const structures::string_array &items); - - void process_comment(const basis::astring ¤t_line_in); - - inner_mark_tree *find_parent(const basis::astring &parent_name); - // locates the parent called "parent_name" given the context that - // we've saved about the last parent. - - static bool advance(int &index, const basis::astring &check, const basis::astring &finding); - //!< moves the "index" forward if the "finding" string is the head of "check". - - static int find_prune_point(const basis::astring &to_prune); - //!< attempts to locate the real start of the root URL in "to_prune". - - // these provide access to the information held about the tree... - - inner_mark_tree &access_root(); // allows access to the root of the tree. - - int link_count() const { return _link_count; } - - int category_count() const { return _category_count; } - -// public data members... currently this is used outside the class. - int _line_number; // the current line in the database. - -private: - inner_mark_tree *_mark_tree; // our tree of categories. - int _link_count; // number of links. - int _category_count; // number of categories. - inner_mark_tree *_last_parent; // the last parent we saw. - inner_mark_tree *_last_node; // the last node we touched. - symbol_int *_links_seen; // URLs we've seen. - structures::string_table *_category_names; // used to enforce uniqueness of categories. -}; - -//////////////////////////////////////////////////////////////////////////// - -class link_record -{ -public: - basis::astring _description; - basis::astring _url; - int _uid; - - link_record(const basis::astring &description, const basis::astring &url, int uid) - : _description(description), _url(url), _uid(uid) {} -}; - -//////////////////////////////////////////////////////////////////////////// - -class listo_links : public structures::amorph -{ -public: - listo_links(); - - void add(link_record *new_rec, bool sort = true); - -private: - int _next_index; // tracks where we've added unsorted items. -}; - -//////////////////////////////////////////////////////////////////////////// - -class inner_mark_tree : public nodes::symbol_tree -{ -public: - listo_links _links; // the list held at this node. - int _uid; // the unique identifier of this node. - - inner_mark_tree(const basis::astring &node_name, int uid, int max_bits = 2) - : nodes::symbol_tree(node_name, max_bits), _uid(uid) {} - -}; - -//////////////////////////////////////////////////////////////////////////// - -#endif - diff --git a/core/applications/bookmark_tools/example_crash_file_for_marks.csv b/core/applications/bookmark_tools/example_crash_file_for_marks.csv deleted file mode 100644 index 3bbff089..00000000 --- a/core/applications/bookmark_tools/example_crash_file_for_marks.csv +++ /dev/null @@ -1,23 +0,0 @@ -"L","Shawn's Occultism and Religion Resources","Eclectic","http://valen.dws.acs.cmu.edu/occult/" -"L","Software, Unix and Music - Paladin Corporation","Research","http://www.paladincorp.com.au/" -"L","southwest indian foundation","unsorted","http://www.southwestindia.com" -"L","Sparptoe.com CD's","Music Archives","http://www.sharptoe.com/toe/cds.htm" -"L","Starbase - Formerly Premia Corporation","Tools","http://www.premia.com/" -"L","superpages.com's BigBook","People","http://bigbook.com/" -"L","Technische Universität Darmstadt","schools","http://www.th-darmstadt.de/" -"L","The Art Bell Web Site","Radio","http://www.artbell.com/" -"L","The iBrator.","Cyber","http://www.briarskin.com/humor/ibrator/" -"L","The Louvre","museums","http://louvre.edu" -"L","The Morrígan's WebAerie","Celtic","http://morrigan.alabanza.com/" -"L","The Net Censorship Dilemma: Liberty or Tyranny","Legalizing Freedom","http://rene.efa.org.au/liberty/" -"L","TombTown home page","web","http://www.tombtown.com/" -"L","Tom Valesky's Old Old Home On the Wild, Wild Worldwide Web","Thomas Valesky","http://www.site.gmu.edu/tvalesky/" -"L","Tuning Apache for Performance","apache","http://php.weblogs.com/tuning_apache_unix" -"L","VNC - Virtual Network Computing from ATT Laboratories Cambridge","ent_man","http://www.uk.research.att.com/vnc/" -"L","Welcome to Caligari Creator of trueSpace","Tools","http://www.caligari.com/" -"L","Welcome to QGPC Home Page","Chemicals","http://www.qgpc.com.qa/" -"L","Welcome to the Inquirer","papers","http://www.theinquirer.net/" -"L","Welcome To The Sequoia Bison Society!","Herd","http://www.sequoiabison.org/" -"L","Welcome to Virtual Pooh Sticks","Automata","http://pooh.muscat.co.uk/pooh-sticks/" -"L","Welcome to Webcorp Multimedia!","Sounds","http://www.webcorp.com/sounds/" -"L","Welcome to web.life.sucks","Nauseating","http://www.alt-life-sucks.org/" diff --git a/core/applications/bookmark_tools/js_marks_maker.cpp b/core/applications/bookmark_tools/js_marks_maker.cpp deleted file mode 100644 index d32f50f4..00000000 --- a/core/applications/bookmark_tools/js_marks_maker.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/*****************************************************************************\ -* * -* Name : marks_maker_javascript * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Turns a link database in HOOPLE format into a web page, when given a * -* suitable template file. The template file must have the phrase: * -* $INSERT_LINKS_HERE * -* at the point where the generated links are supposed to be stored. * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace nodes; -using namespace structures; -using namespace textual; -using namespace timely; - -//#define DEBUG_MARKS - // uncomment to have more debugging noise. - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ - a_sprintf("line %d: ", _categories._line_number) + s, ALWAYS_PRINT) - -const int MAX_FILE_SIZE = 4 * MEGABYTE; - // the largest file we'll read. - -//////////////////////////////////////////////////////////////////////////// - -class marks_maker_javascript : public application_shell -{ -public: - marks_maker_javascript() : application_shell(), _need_closure(false), - _loader_count(0), _link_spool(0), _functions_pending(0) {} - DEFINE_CLASS_NAME("marks_maker_javascript"); - virtual int execute(); - int print_instructions(const filename &program_name); - - int write_marks_page(const astring &output_filename, - const astring &template_filename); - // given a tree of links, this writes out a web page to "output_filename" - // using a template file "template_filename". - -private: - bookmark_tree _categories; // our tree of categories. - bool _need_closure; // true if our
needs a closure. - int _loader_count; // count of the loader functions. - int _link_spool; // count of which link we're writing. - stack _functions_pending; // used for javascript node functions. - -//this needs to gather any strings that would have gone into functions. -//instead, they need to be written into the current node's string. -//when a new function def would be seen, then we need to push a new node -//for accumulating the text. - - // these handle outputting text for categories and links. - void write_category(inner_mark_tree *node, astring &output); - void write_link(inner_mark_tree *node, const link_record &linko, - astring &output); -}; - -//////////////////////////////////////////////////////////////////////////// - -int marks_maker_javascript::print_instructions(const filename &program_name) -{ - a_sprintf to_show("%s:\n\ -This program needs three filenames as command line parameters. The -i flag\n\ -is used to specify the input filename, the -t flag specifies a template web\n\ -page which is used as the wrapper around the links, and the -o flag specifies\n\ -the web page to be created. The input file is expected to be in the HOOPLE\n\ -link database format. The output file will be created from the template file\n\ -by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\ -formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\ -will be replaced with the date when the output file is regenerated.\n\ -The HOOPLE link format is documented here:\n\ - http://hoople.org/guides/link_database/format_manifesto.txt\n\ -", program_name.basename().raw().s(), program_name.basename().raw().s()); - program_wide_logger::get().log(to_show, ALWAYS_PRINT); - return 12; -} - -void marks_maker_javascript::write_category(inner_mark_tree *node, astring &output) -{ -// FUNCDEF("write_category"); - // output a javascript line for the category. - - int node_num = node->_uid; - inner_mark_tree *parent = dynamic_cast(node->parent()); - int parent_node = parent? parent->_uid : -1; - // the parent node for root is a negative one. - astring chewed_name = node->name(); - for (int i = chewed_name.end(); i >= 0; i--) { - // escape any raw single quotes that we see. - if (chewed_name[i] == '\'') { - chewed_name.zap(i, i); - chewed_name.insert(i, "\\'"); - } - } - output += a_sprintf(" b.add(%d, %d, '%s');\n", node_num, parent_node, - chewed_name.s()); -} - -void marks_maker_javascript::write_link(inner_mark_tree *node, const link_record &linko, - astring &output) -{ -// FUNCDEF("write_link"); - // write a javascript link definition. - int parent_node = node->_uid; - astring chewed_name = linko._description; - for (int i = chewed_name.end(); i >= 0; i--) { - // escape any raw single quotes that we see. - if (chewed_name[i] == '\'') { - chewed_name.zap(i, i); - chewed_name.insert(i, "\\'"); - } - } - - if (!linko._url) { - // this just appears to be a comment line. - if (!linko._description) return; // it's a nothing line. - -/* -//hmmm: probably not what we want. -//hmmm: why not, again? - output += linko._description; - output += "
"; - output += parser_bits::platform_eol_to_chars(); -*/ - return; - } - - // generate a function header if the number of links is a multiple of 100. - if (! (_link_spool % 100) ) { - if (_link_spool) { - // close out the previous function and set a timeout. - output += " setTimeout('run_tree_loaders()', 0);\n"; - output += "}\n"; - } - - output += a_sprintf("function tree_loader_%d() {\n", _loader_count++); - } - _link_spool++; - - output += a_sprintf(" b.add(%d, %d, '%s', '%s');\n", - linko._uid, parent_node, chewed_name.s(), linko._url.s()); -} - -int marks_maker_javascript::execute() -{ -// FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - - command_line cmds(_global_argc, _global_argv); // process the command line parameters. - astring input_filename; // we'll store our link database name here. - astring output_filename; // where the web page we're creating goes. - astring template_filename; // the wrapper html code that we'll stuff. - if (!cmds.get_value('i', input_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('o', output_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('t', template_filename, false)) - return print_instructions(cmds.program_name()); - - BASE_LOG(astring("input file: ") + input_filename); - BASE_LOG(astring("output file: ") + output_filename); - BASE_LOG(astring("template file: ") + template_filename); - - int ret = _categories.read_csv_file(input_filename); - if (ret) return ret; - - ret = write_marks_page(output_filename, template_filename); - if (ret) return ret; - - return 0; -} - -int marks_maker_javascript::write_marks_page(const astring &output_filename, - const astring &template_filename) -{ - FUNCDEF("write_marks_page"); - astring long_string; - // this is our accumulator of links. it is the semi-final result that will - // be injected into the template file. - - // add our target layer so that we can write to a useful place. - long_string += "
Marks Target
\n"; - - // add the tree style and creation of the tree object into the text. - long_string += "\n
\n"; - long_string += "\n"; - long_string += "

open all | " - "close all

\n"; - long_string += "
\n"; - - byte_filer template_file(template_filename, "r"); - astring full_template; - if (!template_file.good()) - non_continuable_error(class_name(), func, "the template file could not be opened"); - template_file.read(full_template, MAX_FILE_SIZE); - template_file.close(); - - // javascript output needs some extra junk added to the header section. - int indy = full_template.ifind(""); - if (negative(indy)) - non_continuable_error(class_name(), func, "the template file is missing " - "a declaration"); -//hmmm: the path here must be configurable! - full_template.insert(indy + 8, "\n\n" - "\n" - "\n"); - - // replace the tag with the long string we created. - bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string); - if (!found_it) - non_continuable_error(class_name(), func, "the template file is missing " - "the insertion point"); - full_template.replace("$TODAYS_DATE", time_stamp::notarize(true)); - - filename outname(output_filename); - if (outname.exists()) { - non_continuable_error(class_name(), func, astring("the output file ") - + output_filename + " already exists. It would be over-written if " - "we continued."); - } - - byte_filer output_file(output_filename, "w"); - if (!output_file.good()) - non_continuable_error(class_name(), func, "the output file could not be opened"); - // write the newly generated web page out now. - output_file.write(full_template); - output_file.close(); - -// show the tree. -// BASE_LOG(""); -// BASE_LOG(_categories.access_root().text_form()); - - BASE_LOG(a_sprintf("wrote %d links in %d categories.", - _categories.link_count(), _categories.category_count())); - BASE_LOG(astring("")); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(marks_maker_javascript, ) - diff --git a/core/applications/bookmark_tools/link_parser.cpp b/core/applications/bookmark_tools/link_parser.cpp deleted file mode 100644 index 84467b5e..00000000 --- a/core/applications/bookmark_tools/link_parser.cpp +++ /dev/null @@ -1,518 +0,0 @@ -/*****************************************************************************\ -* * -* Name : link_parser * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Processes html files and finds the links. A database in the HOOPLE * -* link format is created from the links found. * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// Notes: -// -// the standard link structure in html is similar to this: -// Link Name and Launching Point -// -// the standard we adopt for section titles is that it must be a heading -// marker. that formatting looks like this, for example: -//

The Section Title:

- -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -//#define DEBUG_LINK_PARSER - // uncomment for noisier run to seek problems. - -//////////////////////////////////////////////////////////////////////////// - -const int MAX_FILE_SIZE = 4 * MEGABYTE; - // this is the largest html file size we will process. - -//////////////////////////////////////////////////////////////////////////// - -// a macro that increments the position in the string and restarts the loop. -#define INCREM_N_GO { curr_index++; continue; } - -// puts the current character on the intermediate string. -#define ADD_INTERMEDIATE { \ - char add_in = full_contents[curr_index]; \ - if ( (add_in == '<') || (add_in == '>') ) { \ - add_in = '-'; \ - } \ - intermediate_text += add_in; \ -} - -// returns a character in lower-case, if 'a' is in upper case. -char normalize_char(char a) -{ - if ( (a >= 'A') && (a <= 'Z') ) return a + 'a' - 'A'; - return a; -} - -// returns true if the two characters are the same, ignoring upper/lower case. -bool caseless_equals(char a, char b) { return normalize_char(a) == normalize_char(b); } - -// a macro that skips all characters until the specified one is seen. -#define JUMP_TO_CHAR(to_find, save_them) { \ - while ( (curr_index < full_contents.length()) \ - && !caseless_equals(to_find, full_contents[curr_index]) ) { \ - if (save_them) ADD_INTERMEDIATE; \ - curr_index++; \ - } \ -} - -// increments the state, the current character and restarts the loop. -#define NEXT_STATE_INCREM { \ - state = parsing_states(state+1); /* move forward in states. */ \ - curr_index++; \ - continue; \ -} - -// cleans out the disallowed characters in the string provided. -#define CLEAN_UP_NAUGHTY(s) { \ - while (s.replace("\n", " ")) {} \ - while (s.replace("\r", "")) {} \ - s.strip_spaces(); \ -} - -//was before the strip_spaces code above. -/* - int indy = s.find("--"); \ - while (non_negative(indy)) { \ - s[indy] = ' '; / * replace the first dash with a space. * / \ - for (int i = indy + 1; i < s.length(); i++) { \ - if (s[i] != '-') break; \ - s.zap(i, i); \ - i--; \ - } \ - indy = s.find("--"); \ - } \ - while (s.replace(" ", " ")) {} \ -*/ - -// cleans up underscores in areas that are supposed to be english. -#define MAKE_MORE_ENGLISH(s) \ - s.replace_all('_', ' ') - -void strain_out_html_codes(astring &to_edit) -{ - for (int i = 0; i < to_edit.length(); i++) { - if (to_edit[i] != '<') continue; - // found a left bracket. - int indy = to_edit.find('>', i); - if (negative(indy)) return; // bail out, unexpected unmatched bracket. - to_edit.zap(i, indy); - i--; // skip back to reconsider current place. - } -} - -// writes out the currently accumulated link info. -#define WRITE_LINK { \ - /* clean naughty characters out of the names. */ \ - CLEAN_UP_NAUGHTY(url_string); \ - CLEAN_UP_NAUGHTY(name_string); \ - /* output a link in the HOOPLE format. */ \ - astring to_write = "\"L\",\""; \ - to_write += translate_web_chars(name_string); \ - to_write += "\",\""; \ - to_write += abbreviate_category(last_heading); \ - to_write += "\",\""; \ - to_write += translate_web_chars(url_string); \ - to_write += "\"\n"; \ - output_file.write(to_write); \ - _link_count++; \ -} -//was after second clean up naughty -/*argh yuck... if (url_string.ends(name_string)) { \ - / * handle the name being boring. replace with the intermediate text. * / \ - MAKE_MORE_ENGLISH(intermediate_text); \ - strain_out_html_codes(intermediate_text); \ - CLEAN_UP_NAUGHTY(intermediate_text); \ - if (intermediate_text.length()) \ - name_string = intermediate_text; \ - } \ -*/ - -// writes out the current section in the HOOPLE format. -// currently the parent category is set to Root. -#define WRITE_SECTION { \ - CLEAN_UP_NAUGHTY(last_heading); /* clean the name. */ \ - /* output a category definition. */ \ - astring to_write = "\"C\",\""; \ - to_write += translate_web_chars(last_heading); \ - to_write += "\",\""; \ - to_write += abbreviate_category(last_parents.top()); \ - to_write += "\"\n"; \ - output_file.write(to_write); \ - _category_count++; \ -} - -// clears our accumulator strings. -#define RESET_STRINGS { \ - url_string = astring::empty_string(); \ - name_string = astring::empty_string(); \ - intermediate_text = astring::empty_string(); \ -} - -//////////////////////////////////////////////////////////////////////////// - -class link_parser : public application_shell -{ -public: - link_parser(); - DEFINE_CLASS_NAME("link_parser"); - virtual int execute(); - int print_instructions(const filename &program_name); - -private: - int _link_count; // number of links. - int _category_count; // number of categories. - - astring url_string; // the URL we've parsed. - astring name_string; // the name that we've parsed for the URL. - astring last_heading; // the last name that was set for a section. - stack last_parents; // the history of the parent names. - astring intermediate_text; // strings we saw before a link. - - astring heading_num; - // this string form of a number tracks what kind of heading was started. - - astring abbreviate_category(const astring &simplify); - // returns the inner category nickname if the category has one. - - astring translate_web_chars(const astring &vervoom); - // translates a few web chars that are safe for csv back into their non-encoded form. -}; - -//////////////////////////////////////////////////////////////////////////// - -link_parser::link_parser() -: application_shell(), - _link_count(0), - _category_count(0), - last_heading("Root"), - last_parents() -{ - last_parents.push(last_heading); // make sure we have at least one level. -} - -int link_parser::print_instructions(const filename &program_name) -{ - a_sprintf to_show("%s:\n\ -This program needs two filenames as command line parameters. The -i flag\n\ -is used to specify the input filename and the -o flag specifies the output\n\ -file to be created. The input file is expected to be an html file\n\ -containing links to assorted web sites. The links are gathered, along with\n\ -descriptive text that happens to be near them, to create a link database in\n\ -the HOOPLE link format and write it to the output file. HOOPLE link format\n\ -is basically a CSV file that defines the columns 1-4 for describing either\n\ -link categories (which support hierarchies) or actual links (i.e., URLs of\n\ -interest). The links are written to a CSV file in the standard HOOPLE link\n\ -The HOOPLE link format is documented here:\n\ - http://hoople.org/guides/link_database/format_manifesto.txt\n\ -", program_name.basename().raw().s(), program_name.basename().raw().s()); - program_wide_logger::get().log(to_show, ALWAYS_PRINT); - return 12; -} - -astring link_parser::abbreviate_category(const astring &simplify) -{ - astring to_return; - astring name_portion; - bookmark_tree::break_name(simplify, name_portion, to_return); - if (!to_return) return name_portion; - return to_return; -} - -astring link_parser::translate_web_chars(const astring &vervoom) -{ - astring to_return = vervoom; - to_return.replace_all("&", "&"); - to_return.replace_all("ä", "ä"); - to_return.replace_all("©", "(c)"); - to_return.replace_all("é", "é"); - to_return.replace_all("«", "--"); - to_return.replace_all("‘", "'"); - to_return.replace_all("“", "'"); - to_return.replace_all("—", "--"); - to_return.replace_all("–", "--"); - to_return.replace_all(" ", " "); - to_return.replace_all("»", "--"); - to_return.replace_all("”", "'"); - to_return.replace_all("’", "'"); - - to_return.replace_all("%7E", "~"); - to_return.replace_all("%28", "("); - to_return.replace_all("%29", ")"); - return to_return; -} - -int link_parser::execute() -{ - FUNCDEF("main"); - command_line cmds(_global_argc, _global_argv); // process the command line parameters. - astring input_filename; // we'll store our bookmarks file's name here. - astring output_filename; // where the processed marks go. - if (!cmds.get_value('i', input_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('o', output_filename, false)) - return print_instructions(cmds.program_name()); - - BASE_LOG(astring("input file: ") + input_filename); - BASE_LOG(astring("output file: ") + output_filename); - - astring full_contents; - byte_filer input_file(input_filename, "r"); - if (!input_file.good()) - non_continuable_error(class_name(), func, "the input file could not be opened"); - input_file.read(full_contents, MAX_FILE_SIZE); - input_file.close(); - - filename outname(output_filename); - if (outname.exists()) { - non_continuable_error(class_name(), func, astring("the output file ") - + output_filename + " already exists. It would be over-written if " - "we continued."); - } - - byte_filer output_file(output_filename, "w"); - if (!output_file.good()) - non_continuable_error(class_name(), func, "the output file could not be opened"); - - enum parsing_states { - // the states below are order dependent; do not change the ordering! - SEEKING_LINK_START, // looking for the beginning of an html link. - SEEKING_HREF, // finding the href portion of the link. - GETTING_URL, // chowing on the URL portion of the link. - SEEKING_NAME, // finding the closing bracket of the . - GETTING_NAME, // chowing down on characters in the link's name. - SEEKING_CLOSURE, // looking for the to end the link. - // there is a discontinuity after SEEKING_CLOSURE, but then the following - // states are also order dependent. - SAW_TITLE_START, // the beginning of a section heading was seen. - GETTING_TITLE, // grabbing characters in the title. - // new text processing states. - SAW_NESTING_INCREASE, // a new nesting level has been achieved. - SAW_NESTING_DECREASE, // we exited from a former nesting level. - }; - - int curr_index = 0; - parsing_states state = SEEKING_LINK_START; - while (curr_index < full_contents.length()) { - switch (state) { - case SEEKING_LINK_START: - // if we don't see a less-than, then it's not the start of html code, - // so we'll ignore it for now. - if (full_contents[curr_index] != '<') { - ADD_INTERMEDIATE; - INCREM_N_GO; - } - // found a left angle bracket, so now we need to decided where to go next for parsing - // the html coming up. - curr_index++; - // see if this is a heading. if so, we can snag the heading name. - if (caseless_equals('h', full_contents[curr_index])) { -#ifdef DEBUG_LINK_PARSER - LOG("into the '= '0') && (next <= '9') ) { - // we found our proper character for starting a heading. we need - // to jump into that state now. we'll leave the cursor at the - // beginning of the number. - state = SAW_TITLE_START; - INCREM_N_GO; - } - } - // check if they're telling us a new indentation level of the type we care about. - if (caseless_equals('d', full_contents[curr_index])) { -#ifdef DEBUG_LINK_PARSER - LOG("into the ' tag. - char next = full_contents[curr_index + 1]; - if (caseless_equals(next, 'l')) { -#ifdef DEBUG_LINK_PARSER - LOG("into the ' tag. - if ( caseless_equals(full_contents[curr_index + 1], 'd') - && caseless_equals(full_contents[curr_index + 2], 'l') ) { -#ifdef DEBUG_LINK_PARSER - LOG("into the '', false); - continue; - } -#ifdef DEBUG_LINK_PARSER - LOG("into the final case, the '', false); - continue; - } - // this looks like an address so find the start of the href. - NEXT_STATE_INCREM; - break; - case SAW_NESTING_INCREASE: - last_parents.push(last_heading); -#ifdef DEBUG_LINK_PARSER - LOG(a_sprintf("nesting inwards, new depth %d", last_parents.size())); -#endif - JUMP_TO_CHAR('>', false); - state = SEEKING_LINK_START; - break; - case SAW_NESTING_DECREASE: - last_parents.pop(); -#ifdef DEBUG_LINK_PARSER - LOG(a_sprintf("nesting outwards, new depth %d", last_parents.size())); -#endif - JUMP_TO_CHAR('>', false); - state = SEEKING_LINK_START; - break; - case SEEKING_HREF: - JUMP_TO_CHAR('h', false); // find the next 'h' for "href". - curr_index++; - if (!caseless_equals('r', full_contents[curr_index])) continue; - curr_index++; - if (!caseless_equals('e', full_contents[curr_index])) continue; - curr_index++; - if (!caseless_equals('f', full_contents[curr_index])) continue; - curr_index++; - if (full_contents[curr_index] != '=') continue; - curr_index++; - if (full_contents[curr_index] != '"') continue; - // whew, got through the word href and the assignment. the rest - // should all be part of the link. - NEXT_STATE_INCREM; - break; - case GETTING_URL: - // as long as we don't see the closure of the quoted string for the - // href, then we can keep accumulating characters from it. - if (full_contents[curr_index] == '"') NEXT_STATE_INCREM; - url_string += full_contents[curr_index]; - INCREM_N_GO; // keep chewing on it in this same state. - break; - case SEEKING_NAME: - JUMP_TO_CHAR('>', false); // find closing bracket. - NEXT_STATE_INCREM; // now start grabbing the name characters. - break; - case GETTING_NAME: - // we have to stop grabbing name characters when we spy a new code - // being started. - if (full_contents[curr_index] == '<') { - // if we see a closing command, then we assume it's the one we want. - if (full_contents[curr_index + 1] == '/') - NEXT_STATE_INCREM; - // if we see html inside the name, we just throw it out. - JUMP_TO_CHAR('>', false); - curr_index++; - continue; - } - name_string += full_contents[curr_index]; - INCREM_N_GO; // keep chewing on it in this same state. - break; - case SEEKING_CLOSURE: - JUMP_TO_CHAR('>', false); // find the closure of the html code. - // write the link out now. - WRITE_LINK; - // clean out our accumulated strings. - RESET_STRINGS; - state = SEEKING_LINK_START; - INCREM_N_GO; - break; - case SAW_TITLE_START: - heading_num = full_contents.substring(curr_index, curr_index); - JUMP_TO_CHAR('>', false); - NEXT_STATE_INCREM; // start eating the name. - break; - case GETTING_TITLE: { - int indy = full_contents.find('<', curr_index); - if (negative(indy)) { - state = SEEKING_LINK_START; // too weird, go back to start. - continue; - } - // push the last title if it differs from the top of the stack. - last_heading = full_contents.substring(curr_index, indy - 1); - WRITE_SECTION; - JUMP_TO_CHAR('<', false); // now find the start of the header closure. - JUMP_TO_CHAR('>', false); // now find the end of the header closure. - RESET_STRINGS; - state = SEEKING_LINK_START; // successfully found section name. - break; - } - default: - non_continuable_error(class_name(), func, "entered erroneous state!"); - } - } - - if (url_string.t()) WRITE_LINK; - - output_file.close(); - - BASE_LOG(a_sprintf("wrote %d links in %d categories.", _link_count, - _category_count)); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(link_parser, ) - diff --git a/core/applications/bookmark_tools/makefile b/core/applications/bookmark_tools/makefile deleted file mode 100644 index 655c9373..00000000 --- a/core/applications/bookmark_tools/makefile +++ /dev/null @@ -1,14 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -PROJECT = bookmark_tools -TYPE = application -SOURCE = bookmark_tree.cpp bookmark_version.rc -LOCAL_LIBS_USED += application configuration filesystem loggers mathematics nodes \ - processes textual timely structures basis -TARGETS = js_marks_maker.exe link_parser.exe marks_checker.exe marks_maker.exe marks_sorter.exe -USE_CURL = t - -include cpp/rules.def - diff --git a/core/applications/bookmark_tools/marks_checker.cpp b/core/applications/bookmark_tools/marks_checker.cpp deleted file mode 100644 index 1e2c6f07..00000000 --- a/core/applications/bookmark_tools/marks_checker.cpp +++ /dev/null @@ -1,453 +0,0 @@ -/*****************************************************************************\ -* * -* Name : marks_checker * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Checks on the existence of the links listed in a HOOPLE format link * -* database and reports the bad ones. * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace algorithms; -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace nodes; -using namespace mathematics; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; - -//#define DEBUG_MARKS - // uncomment to have more debugging noise. - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(astring(s), ALWAYS_PRINT) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ - a_sprintf("line %d: ", _categories._line_number) + s) - -const int PAUSEY_SNOOZE = 200; - // how long we sleep if there are too many threads running already. - -const int MAXIMUM_THREADS = 14; - // we allow this many simultaneous web checks at a time. - -const int MAXIMUM_READ = 1008; - // we only download this much of the link. this avoids huge downloads of - // very large sites. - -const int MAXIMUM_ATTEMPTS = 2; - // we'll retry the check if we get an actual error instead of an http error - // code. when a name can't be found in the DNS, it sometimes comes back - // shortly after it was checked. if we see we can't reach the domain after - // this many tries, then we give up on the address. - -const int TIME_PER_REQUEST_IN_SEC = 60 * 6; - // limit our requests to this long of a period. then we will not be - // stalled forever by uncooperative websites. - -const char *FAKE_AGENT_STRING = "FredWeb/7.0 (X11; U; Linux i686; " - "en-US; rv:1.8.19) Flecko/20081031"; - // we use this as our agent type, since some sites won't treat us fairly - // if they think we're robots when we're checking their site health. -//still true? - // for example (ahem!), the usa today websites. - -//////////////////////////////////////////////////////////////////////////// - -class safe_int_array -{ -public: - safe_int_array() : _lock(), _list(0) {} - - void add(int to_add) { -///BASE_LOG(a_sprintf("adding %d to list", to_add)); - auto_synchronizer l(_lock); - _list += to_add; - } - - int length() { - auto_synchronizer l(_lock); - return _list.length(); - } - - basis::int_array make_copy() { - auto_synchronizer l(_lock); - return _list; - } - -private: - basis::mutex _lock; - basis::int_array _list; -}; - -//////////////////////////////////////////////////////////////////////////// - -class marks_checker : public application_shell -{ -public: - marks_checker() - : application_shell(), _check_redirection(false), - _max_threads(MAXIMUM_THREADS), _null_file(filename::null_device(), "w") - {} - - DEFINE_CLASS_NAME("marks_checker"); - virtual int execute(); - int print_instructions(const filename &program_name); - - int test_all_links(); - // goes through the tree of links and tests them all. - - int check_link(const astring &url, astring &error_msg); - // synchronously checks the "url" for health. the return value is zero - // on success or an HTTP error code on failure. - - void write_new_files(); - // writes out the two new files given the info accumulated so far. - -private: - bookmark_tree _categories; // our tree of categories. - safe_int_array _bad_lines; // lines with bad contents. - thread_cabinet _checkers; // threads checking on links. - astring _input_filename; // we'll store our link database name here. - astring _output_filename; // where the list of good links is stored. - astring _bad_link_filename; // garbage dump of bad links. - bool _check_redirection; // true if redirection is disallowed. - int _max_threads; // the most threads we'll allow at once. - byte_filer _null_file; // we'll use this for trashing output data. - - static void handle_OS_signal(int sig_id); - // handles break signals from the user. -}; - -//////////////////////////////////////////////////////////////////////////// - -class checking_thread : public ethread -{ -public: - checking_thread(const link_record &link_info, safe_int_array &bad_lines, - marks_checker &checker) - : ethread(), _bad_lines(bad_lines), _checker(checker), _info(link_info) {} - - void perform_activity(void *formal(ptr)) { - astring message; - int ret = _checker.check_link(_info._url, message); - if (ret != 0) { - astring complaint = a_sprintf("Bad Link at line %d:", _info._uid) - += parser_bits::platform_eol_to_chars(); - const astring spacer(' ', 4); - complaint += spacer + _info._url += parser_bits::platform_eol_to_chars(); - complaint += spacer + _info._description += parser_bits::platform_eol_to_chars(); - complaint += spacer + "error: " += message; - BASE_LOG(complaint); -if ( (_info._uid> 100000) || (_info._uid < 0) ) { -BASE_LOG(a_sprintf("somehow got bogus line number! %d", _info._uid)); -return; -} - _bad_lines.add(_info._uid); // list ours as bad. - } - } - -private: - safe_int_array &_bad_lines; - marks_checker &_checker; - link_record _info; -}; - -//////////////////////////////////////////////////////////////////////////// - -int marks_checker::print_instructions(const filename &program_name) -{ - a_sprintf to_show("%s:\n\ -This program needs three filenames as command line parameters. The -i flag\n\ -is used to specify the input filename. The -o flag specifies the file where\n\ -where the good links will be written. The -b flag specifies the file where\n\ -the bad links are written. The optional flag --no-redirs can be used to\n\ -disallow web-site redirection, which will catch when the site has changed\n\ -its location. Note that redirection is not necessarily an error, but it\n\ -instead may just be a link that needs its URL modified. It is recommended\n\ -that you omit this flag in early runs, in order to only locate definitely\n\ -dead links. Then later checking runs can find any sites that were redirected\n\ -or being routed to a dead link page which doesn't provide an error code.\n\ -The optional flag --threads with a parameter will set the maximum number of\n\ -threads that will simultaneously check on links.\n\ -The input file is expected to be in the HOOPLE link database format.\n\ -The HOOPLE link format is documented here:\n\ - http://hoople.org/guides/link_database/format_manifesto.txt\n\ -", program_name.basename().raw().s(), program_name.basename().raw().s()); - program_wide_logger::get().log(to_show, ALWAYS_PRINT); - return 12; -} - -// this function just eats any data it's handed. -size_t data_sink(void *formal(ptr), size_t size, size_t number, void *formal(stream)) -{ return size * number; } - -int marks_checker::check_link(const astring &url, astring &error_msg) -{ - int to_return = -1; - - CURL *cur = curl_easy_init(); - - curl_easy_setopt(cur, CURLOPT_URL, url.s()); // set the URL itself. - - curl_easy_setopt(cur, CURLOPT_SSL_VERIFYPEER, 0); - // don't verify SSL certificates. - curl_easy_setopt(cur, CURLOPT_MAXFILESIZE, MAXIMUM_READ); - // limit the download size; causes size errors, which we elide to success. - curl_easy_setopt(cur, CURLOPT_NOSIGNAL, 1); - // don't use signals since it interferes with sleep. - curl_easy_setopt(cur, CURLOPT_TIMEOUT, TIME_PER_REQUEST_IN_SEC); - // limit time allowed per operation. - curl_easy_setopt(cur, CURLOPT_AUTOREFERER, true); - // automatically fill in the referer field when redirected. - - curl_easy_setopt(cur, CURLOPT_WRITEDATA, _null_file.file_handle()); - // set the file handle where we want our downloaded data to go. since - // we're just checking the links, this goes right to the trash. - curl_easy_setopt(cur, CURLOPT_WRITEFUNCTION, data_sink); - // set the function which will be given all the downloaded data. - - curl_easy_setopt(cur, CURLOPT_USERAGENT, FAKE_AGENT_STRING); - // fake being a browser here since otherwise we get no respect. - - curl_easy_setopt(cur, CURLOPT_FTPLISTONLY, 1); - // get only a simple list of files, which allows us to hit ftp sites - // properly. if the normal curl mode is used, we get nothing. - - if (_check_redirection) { - // attempting to quash redirects as being valid. - curl_easy_setopt(cur, CURLOPT_FOLLOWLOCATION, 1); // follow redirects. - curl_easy_setopt(cur, CURLOPT_MAXREDIRS, 0); // allow zero redirects. - } - - int tries = 0; - while (tries++ < MAXIMUM_ATTEMPTS) { - - // we do the error message again every time, since it gets shrunk after - // the web page check and is no longer available where it was in memory. - error_msg = astring(' ', CURL_ERROR_SIZE + 5); - curl_easy_setopt(cur, CURLOPT_ERRORBUFFER, error_msg.s()); - - // set the error message buffer so we know what happened. - - // try to lookup the web page we've been given. - to_return = curl_easy_perform(cur); - - error_msg.shrink(); // just use the message without extra spaces. - - // we turn file size errors into non-errors, since we have set a very - // low file size in order to avoid downloading too much. we really just - // want to check links, not download their contents. - if (to_return == CURLE_FILESIZE_EXCEEDED) to_return = 0; - - if (!to_return) { - // supposedly this is a success, but let's check the result code. - long result; - curl_easy_getinfo(cur, CURLINFO_RESPONSE_CODE, &result); - if (result >= 400) { - error_msg = a_sprintf("received http failure code %d", result); - to_return = -1; - } - break; // this was a successful result, a zero outcome from perform. - } - - time_control::sleep_ms(10 * SECOND_ms); // give it a few more seconds... - } - - curl_easy_cleanup(cur); - - return to_return; -} - -int marks_checker::test_all_links() -{ - FUNCDEF("test_all_links"); - // traverse the tree in prefix order. - tree::iterator itty = _categories.access_root().start(tree::prefix); - tree *curr = NIL; - while ( (curr = itty.next()) ) { - inner_mark_tree *nod = dynamic_cast(curr); - if (!nod) - non_continuable_error(static_class_name(), func, "failed to cast a tree node"); - // iterate on all the links at this node to check them. - for (int i = 0; i < nod->_links.elements(); i++) { - link_record *lin = nod->_links.borrow(i); - if (!lin->_url) continue; // not a link. - - while (_checkers.threads() > _max_threads) { - time_control::sleep_ms(PAUSEY_SNOOZE); - _checkers.clean_debris(); - } - - checking_thread *new_thread = new checking_thread(*lin, _bad_lines, - *this); - unique_int id = _checkers.add_thread(new_thread, true, NIL); - } - } - -BASE_LOG("... finished iterating on tree."); - - // now wait until all the threads are finished. - while (_checkers.threads()) { - time_control::sleep_ms(PAUSEY_SNOOZE); - _checkers.clean_debris(); - } - -BASE_LOG("... finished waiting for all threads."); - - return 0; -} - -void marks_checker::write_new_files() -{ - byte_filer input_file(_input_filename, "r"); - byte_filer output_file(_output_filename, "w"); - byte_filer badness_file(_bad_link_filename, "w"); - - basis::int_array badness = _bad_lines.make_copy(); - shell_sort(badness.access(), badness.length()); - - BASE_LOG("bad links are on lines:"); - astring bad_list; - for (int i = 0; i < badness.length(); i++) { - bad_list += a_sprintf("%d, ", badness[i]); - } - BASE_LOG(bad_list); - - astring buffer; - int curr_line = 0; - while (!input_file.eof()) { - curr_line++; - while (badness.length() && (badness[0] < curr_line) ) { - BASE_LOG(a_sprintf("whacking too low line number: %d", badness[0])); - badness.zap(0, 0); - } - input_file.getline(buffer, 2048); -//make that a constant. - if (badness.length() && (badness[0] == curr_line)) { - // we seem to have found a bad line. - badness_file.write(buffer); - badness.zap(0, 0); // remove the current line number. - } else { - // this is a healthy line. - output_file.write(buffer); - } - - } - input_file.close(); - output_file.close(); - badness_file.close(); -} - -marks_checker *main_program = NIL; - -void marks_checker::handle_OS_signal(int formal(sig_id)) -{ - signal(SIGINT, SIG_IGN); // turn off that signal for now. - BASE_LOG("caught break signal... now writing files."); - if (main_program) main_program->write_new_files(); - BASE_LOG("exiting after handling break."); - main_program = NIL; - exit(0); -} - -int marks_checker::execute() -{ - FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - - main_program = this; // used by our signal handler. - - command_line cmds(_global_argc, _global_argv); // process the command line parameters. - if (!cmds.get_value('i', _input_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('o', _output_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('b', _bad_link_filename, false)) - return print_instructions(cmds.program_name()); - - astring temp; - - // optional flag for checking website redirection. - if (cmds.get_value("no-redirs", temp, false)) { - BASE_LOG("Enabling redirection checking: redirected web sites are reported as bad."); - _check_redirection = true; - } - // optional flag for number of threads. - astring threads; - if (cmds.get_value("threads", threads, false)) { - _max_threads = threads.convert(0); - BASE_LOG(a_sprintf("Maximum threads allowed=%d", _max_threads)); - } - - BASE_LOG(astring("input file: ") + _input_filename); - BASE_LOG(astring("output file: ") + _output_filename); - BASE_LOG(astring("bad link file: ") + _bad_link_filename); - -//hmmm: check if output file already exists. -//hmmm: check if bad file already exists. - -LOG("before reading input..."); - - int ret = _categories.read_csv_file(_input_filename); - if (ret) return ret; // failure during read means we can't do much. - -LOG("after reading input..."); - - signal(SIGINT, handle_OS_signal); - // hook the break signal so we can still do part of the job if they - // interrupt us. - - curl_global_init(CURL_GLOBAL_ALL); // crank up the cURL engine. - - ret = test_all_links(); - - write_new_files(); - main_program = NIL; - - curl_global_cleanup(); // shut down cURL engine again. - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(marks_checker, ) - diff --git a/core/applications/bookmark_tools/marks_maker.cpp b/core/applications/bookmark_tools/marks_maker.cpp deleted file mode 100644 index 8e2cbef5..00000000 --- a/core/applications/bookmark_tools/marks_maker.cpp +++ /dev/null @@ -1,416 +0,0 @@ -////////////// -// Name : marks_maker -// Author : Chris Koeritz -////////////// -// Copyright (c) 2005-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace nodes; -using namespace structures; -using namespace textual; -using namespace timely; - -//#define DEBUG_MARKS - // uncomment to have more debugging noise. - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -const int MAX_FILE_SIZE = 4 * MEGABYTE; - // the largest file we'll read. - -const int SPACING_CHUNK = 4; - // number of spaces per indentation level. - -const int MAX_URL_DISPLAYED = 58; -const int MAX_DESCRIP_DISPLAYED = 72; - -//////////////////////////////////////////////////////////////////////////// - -class marks_maker : public application_shell -{ -public: - marks_maker(); - - enum output_style { - ST_HUMAN_READABLE, - ST_MOZILLA_MARKS, -// ST_JAVASCRIPT_BASED... separate implementation currently. - }; - - int write_marks_page(const astring &output_filename, - const astring &template_filename, output_style way); - // given a tree of links, this writes out a web page to "output_filename" - // using a template file "template_filename". - - DEFINE_CLASS_NAME("marks_maker"); - int print_instructions(const filename &program_name); - virtual int execute(); - -private: - bookmark_tree c_categories; // our tree of categories. - int c_current_depth; // current indentation depth in list. - output_style c_style; // style of marks to write, set after construction. - - void increase_nesting(astring &output); - // adds a new level of nesting to the text. - - void decrease_nesting(astring &output); - // drops out of a level of nesting in the text. - - astring recurse_on_node(inner_mark_tree *nod); - // the main recursive method that spiders down the tree. it is important that it builds - // the string through composition rather than being given a string reference, since it - // expands all sub-trees as it goes. - - void inject_javascript_function(astring &output); - // replaces a special phrase in the template file with our javascript-based link opener. - - void write_category_start(const astring &name, int node_depth, astring &output); - // outputs the text for categories and adjusts the indentation level. - - void write_category_end(int depth, astring &output); - // closes a category appropriately for the nesting depth. - - void write_link(inner_mark_tree *node, const link_record &linko, - astring &output, int depth); - // outputs the text for web links. -}; - -//////////////////////////////////////////////////////////////////////////// - -marks_maker::marks_maker() -: application_shell(), - c_current_depth(0), - c_style(ST_HUMAN_READABLE) -{} - -int marks_maker::print_instructions(const filename &program_name) -{ - a_sprintf to_show("%s:\n\ -This program needs three filenames as command line parameters. The -i flag\n\ -is used to specify the input filename, the -t flag specifies a template web\n\ -page which is used as the wrapper around the links, and the -o flag specifies\n\ -the web page to be created. The input file is expected to be in the HOOPLE\n\ -link database format. The output file will be created from the template file\n\ -by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\ -formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\ -will be replaced with the date when the output file is regenerated. A final\n\ -tag of $INSERT_JAVASCRIPT_HERE is replaced with a link opening function.\n\ -Note that an optional -s flag can specify a value of \"human\" readable\n\ -or \"mozilla\" bookmarks style to specify the style of the output file\n\ -generated.\n\ -The HOOPLE link format is documented here:\n\ - http://hoople.org/guides/link_database/format_manifesto.txt\n\ -", program_name.basename().raw().s(), program_name.basename().raw().s()); - program_wide_logger::get().log(to_show, ALWAYS_PRINT); - return 12; -} - -void marks_maker::increase_nesting(astring &output) -{ - FUNCDEF("increase_nesting"); - int spaces = SPACING_CHUNK * c_current_depth; - c_current_depth++; -#ifdef DEBUG_MARKS - LOG(a_sprintf("++increased depth to %d...", c_current_depth)); -#endif - output += string_manipulation::indentation(spaces); - output += "

"; - output += parser_bits::platform_eol_to_chars(); -} - -void marks_maker::decrease_nesting(astring &output) -{ - FUNCDEF("decrease_nesting"); - c_current_depth--; -#ifdef DEBUG_MARKS - LOG(a_sprintf("--decreased depth to %d...", c_current_depth)); -#endif - int spaces = SPACING_CHUNK * c_current_depth; - output += string_manipulation::indentation(spaces); - output += "

"; - output += parser_bits::platform_eol_to_chars(); -} - -void marks_maker::write_category_start(const astring &name, int node_depth, astring &output) -{ - FUNCDEF("write_category_start"); - - // calculate proper heading number. - int heading_num = node_depth + 1; - astring heading = a_sprintf("%d", heading_num); - // force a weird requirement for mozilla bookmarks, all headings must be set at 3. - if (c_style == ST_MOZILLA_MARKS) heading = "3"; - -#ifdef DEBUG_MARKS - LOG(astring("header [") + name + "] level " + a_sprintf("%d", node_depth)); -#endif - - // output our heading. - output += string_manipulation::indentation(c_current_depth * SPACING_CHUNK); - output += "

MAX_URL_DISPLAYED) { - chomped_url.zap(MAX_URL_DISPLAYED / 2, - chomped_url.length() - MAX_URL_DISPLAYED / 2 - 1); - chomped_url.insert(MAX_URL_DISPLAYED / 2, "..."); - } - } - - astring description = linko._description; - if (c_style != ST_MOZILLA_MARKS) { - // this is chopping the tail off, which seems reasonable for a very long description. - if (description.length() > MAX_DESCRIP_DISPLAYED) { - description.zap(MAX_DESCRIP_DISPLAYED - 1, description.end()); - description += "..."; - } - } - - // new output format, totally clean and simple. description is there - // in readable manner, and it's also a link. plus, this takes up a fraction - // of the space the old way used. - astring indentulus = string_manipulation::indentation(c_current_depth * SPACING_CHUNK); - output += indentulus; - output += "
  • "; - output += ""; - output += description; - output += ""; - - if (c_style != ST_MOZILLA_MARKS) { - output += "   "; - output += ""; - output += "[launch]"; - output += ""; - } - - output += "
  • "; - output += "
    "; - output += parser_bits::platform_eol_to_chars(); -} - -astring marks_maker::recurse_on_node(inner_mark_tree *nod) -{ - FUNCDEF("recurse_on_node"); - astring to_return; - - // print out the category on this node. - write_category_start(nod->name(), nod->depth(), to_return); - - // print the link for all of the ones stored at this node. - for (int i = 0; i < nod->_links.elements(); i++) { - link_record *lin = nod->_links.borrow(i); - write_link(nod, *lin, to_return, nod->depth()); - } - - // zoom down into sub-categories. - for (int i = 0; i < nod->branches(); i++) { - to_return += recurse_on_node((inner_mark_tree *)nod->branch(i)); - } - - // finish this category. - write_category_end(nod->depth(), to_return); - - return to_return; -} - -void marks_maker::inject_javascript_function(astring &output) -{ - FUNCDEF("inject_javascript_function"); - astring scrip = "\n\ -\n\ -\n"; - - bool found_it = output.replace("$INSERT_JAVASCRIPT_HERE", scrip); - if (!found_it) - non_continuable_error(class_name(), func, "the template file is missing " - "the insertion point for '$INSERT_JAVASCRIPT_HERE'"); -} - -int marks_maker::write_marks_page(const astring &output_filename, - const astring &template_filename, output_style style) -{ - FUNCDEF("write_marks_page"); - c_style = style; // set the overall output style here. - astring long_string; - // this is our accumulator of links. it is the semi-final result that will - // be injected into the template file. - - // generate the meaty portion of the bookmarks. - increase_nesting(long_string); - inner_mark_tree *top = (inner_mark_tree *)&c_categories.access_root(); - long_string += recurse_on_node(top); - decrease_nesting(long_string); - - byte_filer template_file(template_filename, "r"); - astring full_template; - if (!template_file.good()) - non_continuable_error(class_name(), func, "the template file could not be opened"); - template_file.read(full_template, MAX_FILE_SIZE); - template_file.close(); - - // spice up the boring template with a nice link opening function. - inject_javascript_function(full_template); - - // replace the tag with the long string we created. - bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string); - if (!found_it) - non_continuable_error(class_name(), func, "the template file is missing " - "the insertion point for '$INSERT_LINKS_HERE'"); - - full_template.replace("$TODAYS_DATE", time_stamp::notarize(true)); - - filename outname(output_filename); - byte_filer output_file(output_filename, "w"); - if (!output_file.good()) - non_continuable_error(class_name(), func, "the output file could not be opened"); - // write the newly generated web page out now. - output_file.write(full_template); - output_file.close(); - -#ifdef DEBUG_MARKS - // show the tree. - BASE_LOG(astring()); - BASE_LOG(astring("the tree, sir...")); - BASE_LOG(astring()); - BASE_LOG(c_categories.access_root().text_form()); -#endif - - BASE_LOG(a_sprintf("wrote %d links in %d categories.", - c_categories.link_count(), c_categories.category_count())); - BASE_LOG(astring("")); - - return 0; -} - -int marks_maker::execute() -{ - FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - - command_line cmds(_global_argc, _global_argv); // process the command line parameters. - astring input_filename; // we'll store our link database name here. - astring output_filename; // where the web page we're creating goes. - astring template_filename; // the wrapper html code that we'll stuff. - astring style_used; // type of output file style to create. - if (!cmds.get_value('i', input_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('o', output_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('t', template_filename, false)) - return print_instructions(cmds.program_name()); - cmds.get_value('s', style_used, false); - if (!style_used) style_used = "human"; - - BASE_LOG(astring("input file: ") + input_filename); - BASE_LOG(astring("output file: ") + output_filename); - BASE_LOG(astring("template file: ") + template_filename); - BASE_LOG(astring("style: ") + style_used); - - filename outname(output_filename); - if (outname.exists()) { - non_continuable_error(class_name(), func, astring("the output file ") - + output_filename + " already exists. It would be over-written if " - "we continued."); - } - - output_style styley = ST_HUMAN_READABLE; - if (style_used == astring("mozilla")) styley = ST_MOZILLA_MARKS; - - int ret = c_categories.read_csv_file(input_filename); - if (ret) return ret; - - ret = write_marks_page(output_filename, template_filename, styley); - if (ret) return ret; - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(marks_maker, ) - diff --git a/core/applications/bookmark_tools/marks_sorter.cpp b/core/applications/bookmark_tools/marks_sorter.cpp deleted file mode 100644 index b793d373..00000000 --- a/core/applications/bookmark_tools/marks_sorter.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/*****************************************************************************\ -* * -* Name : marks_sorter * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Processes a link database in HOOPLE format and generates a new database * -* that is sorted and always uses category nicknames where defined. * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bookmark_tree.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace nodes; -using namespace structures; -using namespace textual; - -//#define DEBUG_MARKS - // uncomment to have more debugging noise. - -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ - a_sprintf("line %d: ", _categories._line_number) + s) - -const int MAX_FILE_SIZE = 4 * MEGABYTE; - // the largest file we'll read. - -//////////////////////////////////////////////////////////////////////////// - -class marks_sorter : public application_shell -{ -public: - marks_sorter() - : application_shell(), _loader_count(0), _link_spool(0) {} - DEFINE_CLASS_NAME("marks_sorter"); - virtual int execute(); - int print_instructions(const filename &program_name); - - int write_new_marks(const astring &output_filename); - // given a tree of links, this writes out a new sorted file to the - // "output_filename". - -private: - bookmark_tree _categories; // our tree of categories. - int _loader_count; // count of the loader functions. - int _link_spool; // count of which link we're writing. -}; - -//////////////////////////////////////////////////////////////////////////// - -int marks_sorter::print_instructions(const filename &program_name) -{ - a_sprintf to_show("%s:\n\ -This program needs two filenames as command-line parameters. The -i flag\n\ -is used to specify the input filename, which is expected to be in the HOOPLE\n\ -link database format. The -o flag specifies the new bookmarks file to be\n\ -created, which will also be in the HOOPLE link format.\n\ -The HOOPLE link format is documented here:\n\ - http://hoople.org/guides/link_database/format_manifesto.txt\n\ -", program_name.basename().raw().s(), program_name.basename().raw().s()); - program_wide_logger::get().log(to_show, ALWAYS_PRINT); - return 12; -} - -int marks_sorter::execute() -{ - FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - - command_line cmds(_global_argc, _global_argv); // process the command line parameters. - astring input_filename; // we'll store our link database name here. - astring output_filename; // where the web page we're creating goes. - if (!cmds.get_value('i', input_filename, false)) - return print_instructions(cmds.program_name()); - if (!cmds.get_value('o', output_filename, false)) - return print_instructions(cmds.program_name()); - - BASE_LOG(astring("input file: ") + input_filename); - BASE_LOG(astring("output file: ") + output_filename); - - filename outname(output_filename); - if (outname.exists()) { - non_continuable_error(class_name(), func, astring("the output file ") - + output_filename + " already exists. It would be over-written if " - "we continued."); - } - - int ret = _categories.read_csv_file(input_filename); - if (ret) return ret; - - ret = write_new_marks(output_filename); - if (ret) return ret; - - return 0; -} - -int marks_sorter::write_new_marks(const astring &output_filename) -{ - FUNCDEF("write_new_marks"); - // open the output file for streaming out the new marks file. - filename outname(output_filename); - byte_filer output_file(output_filename, "w"); - if (!output_file.good()) - non_continuable_error(class_name(), func, "the output file could not be opened"); - - bool just_had_return = false; // did we just see a carriage return? - bool first_line = true; // is this the first line to be emitted? - - // traverse the tree in prefix order. - tree::iterator itty = _categories.access_root().start(tree::prefix); - tree *curr = NIL; // the current node. - - while ( (curr = itty.next()) ) { - inner_mark_tree *nod = (inner_mark_tree *)curr; - // set up a category printout for this node. - string_array cat_list; - cat_list += "C"; - cat_list += nod->name(); - inner_mark_tree *pare = (inner_mark_tree *)nod->parent(); - if (pare) { - astring name_split, nick_split; - _categories.break_name(pare->name(), name_split, nick_split); - if (!nick_split) cat_list += name_split; - else cat_list += nick_split; - } else { - cat_list += ""; - } - - // create a text line to send to the output file. - astring tmp; - list_parsing::create_csv_line(cat_list, tmp); - tmp += "\n"; - if (!just_had_return && !first_line) { - // generate a blank line before the category name. - output_file.write(parser_bits::platform_eol_to_chars()); - } - - // reset the flags after we've checked them. - just_had_return = false; - first_line = false; - - output_file.write(tmp); - // write the actual category definition. - - // print the links for all of the ones stored at this node. - for (int i = 0; i < nod->_links.elements(); i++) { - link_record *lin = nod->_links.borrow(i); - if (!lin->_url) { - // just a comment. - astring descrip = lin->_description; - if (descrip.contains("http:")) { - // we'll clean the html formatting out that we added earlier. - int indy = descrip.find('"'); - if (non_negative(indy)) { - descrip.zap(0, indy); - indy = descrip.find('"'); - if (non_negative(indy)) descrip.zap(indy, descrip.end()); - } - descrip = astring(" ") + descrip; - // add a little spacing. - } - if (descrip.t()) { - output_file.write(astring("#") + descrip + "\n"); - just_had_return = false; - } else { - // this line's totally blank, so we'll generate a blank line. - // we don't want to put in more than one blank though, so we check - // whether we did this recently. - if (!just_had_return) { - output_file.write(parser_bits::platform_eol_to_chars()); - just_had_return = true; // set our flag for a carriage return. - } - } - } else { - // should be a real link. - string_array lnks; - lnks += "L"; - lnks += lin->_description; - // use just the nickname for the parent, if there is a nick. - astring name_split; - astring nick_split; - _categories.break_name(nod->name(), name_split, nick_split); - if (!nick_split) lnks += nod->name(); - else lnks += nick_split; - lnks += lin->_url; - list_parsing::create_csv_line(lnks, tmp); - tmp += "\n"; - output_file.write(tmp); - just_had_return = false; - } - } - } - - output_file.close(); - - BASE_LOG(a_sprintf("wrote %d links in %d categories.", - _categories.link_count(), _categories.category_count())); - BASE_LOG(astring()); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(marks_sorter, ) - diff --git a/core/applications/bookmark_tools/version.ini b/core/applications/bookmark_tools/version.ini deleted file mode 100644 index ef1e3c14..00000000 --- a/core/applications/bookmark_tools/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description = Bookmark Utilities -root = bookmark -name = Bookmark Apps -extension = exe - diff --git a/core/applications/bundler/bundle_creator.cpp b/core/applications/bundler/bundle_creator.cpp deleted file mode 100644 index 5a6722a9..00000000 --- a/core/applications/bundler/bundle_creator.cpp +++ /dev/null @@ -1,1007 +0,0 @@ - -//hmmm: anything related to _stub_size should be kept, but that is where -// we need a redundant search mechanism that can't be fooled so easily -// by modifying exe; make a pattern that will be found and is the first -// place to start looking for manifest. - -/*****************************************************************************\ -* * -* Name : bundle_creator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "common_bundle.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef __WIN32__ - #include -#endif - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace filesystem; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; - -const int CHUNKING_SIZE = 256 * KILOBYTE; - // we'll read this big a chunk from a source file at a time. - -const astring SUBVERSION_FOLDER = ".svn"; - // we don't want to include this in a bundle. - -#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) - -//#define DEBUG_BUNDLER - // uncomment for noisy debugging version. - -// returns the "retval" and mentions that this is a failure at "where". -#define FAIL_RETURN(retval, where) { \ - LOG(astring("failure in ") + where + a_sprintf(", exit=%d", retval)); \ - return retval; \ -} - -//////////////////////////////////////////////////////////////////////////// - -bool true_value(const astring &value) -{ return (!value.equal_to("0")) && (!value.equal_to("false")); } - -//////////////////////////////////////////////////////////////////////////// - -// this structure overrides the manifest_chunk by providing a source string. - -struct bundled_chunk : manifest_chunk -{ - astring _source; //!< where the file comes from on the source system. - virtual ~bundled_chunk() {} -}; - -//////////////////////////////////////////////////////////////////////////// - -// main bundler class. - -class bundle_creator : public application_shell -{ -public: - bundle_creator() - : application_shell(), - _app_name(filename(_global_argv[0]).basename()), - _bundle(NIL), _stub_size(0), _keyword() {} - - virtual ~bundle_creator() { - WHACK(_bundle); - } - - DEFINE_CLASS_NAME("bundle_creator"); - virtual int execute(); - int print_instructions(); - - astring determine_stub_file_and_validate(); - //!< returns the stub file location if it could be successfully located. - - int open_output_file(); - //!< prepares the output file to be written into. - /*!< non-zero return indicates an error. */ - - int read_manifest(); - //!< reads manifest definition specifying files in the bundle. - /*!< creates the list of bundle pieces. */ - - int write_stub_and_toc(); - //!< stuffs the unpacker stub into output file and table of contents. - - int bundle_sources(); - //!< reads all of the input files and dumps them into the bundle. - - int finalize_file(); - //!< puts finishing touches on the output file and closes it. - - int write_offset(); - //!< writes the offset position into the output file. - /*!< this happens at the specially marked location (muftiloc). */ - - int patch_recursive_target(const astring &source, const astring &target, - int manifest_index); - //!< processes the recursive target specified in "curr". - /*!< the manifest_index tells the function where the external caller - is currently working on the manifest. new items will appear just after - that index. */ - - int recurse_into_dir(const astring &source, const astring &target, - int manifest_index); - //!< adds all files from "source" to our list, recurses on dirs. - - int patch_wildcard_target(const astring &source, const astring &target, - int manifest_index); - //!< processes the wildcard bearing target specified in "curr". - /*!< any new source items will get dropped on the end of the manifest. */ - - int add_files_here(directory &dirndl, const astring &source, - const astring &target, int manifest_index); - //!< takes all the files found in "source" and adds them to manifest. - - bool get_file_size(const astring &file, un_int &size, byte_array ×tamp); - //!< returns the file "size" and "timestamp" found for "file". - -private: - astring _app_name; //!< application name for this program. - astring _output_file; //!< what bundle file to create. - astring _manifest_file; //!< the manifest of what's included in bundle. - array _manifest_list; //!< the parsed list of contents. - byte_filer *_bundle; //!< points at the bundled output file. - int _stub_size; //!< where the TOC will be located. - astring _keyword; // set if we were given a keyword on cmd line. -}; - -//////////////////////////////////////////////////////////////////////////// - -int bundle_creator::print_instructions() -{ - BASE_LOG(a_sprintf("\ -%s: This program needs two parameters on the command line.\n\ -The -o flag must point at the bundled output file to create. The -m flag\n\ -must point at a valid manifest file that defines what will be packed into\n\ -the output file. See the example manifest in the bundler example\n\ -(in setup_src/bundle_example) for more information on the required file\n\ -format.\n\ -", _app_name.s())); - return 4; -} - -int bundle_creator::execute() -{ - FUNCDEF("execute"); - - BASE_LOG(astring("starting file bundling at ") + time_stamp::notarize(false)); - - command_line cmds(_global_argc, _global_argv); - astring temp; - if (cmds.get_value('?', temp)) return print_instructions(); - if (cmds.get_value("?", temp)) return print_instructions(); - if (!cmds.get_value('o', _output_file)) return print_instructions(); - if (!cmds.get_value('m', _manifest_file)) return print_instructions(); - - if (filename(_output_file).exists()) { - BASE_LOG(a_sprintf("\ -%s: The output file already exists. Please move it out of\n\ -the way; this program will not overwrite existing files.\n", -_app_name.s())); - return 3; - } - - if (!filename(_manifest_file).exists()) { - BASE_LOG(a_sprintf("\ -%s: The manifest file does not exist. This program cannot do anything\n\ -without a valid packing manifest.\n", _app_name.s())); - return 2; - } - - // test this early on so we don't waste time uselessly. - astring stub_file_okay = determine_stub_file_and_validate(); - if (!stub_file_okay) { - BASE_LOG(a_sprintf("\ -%s: The unpacking stub file does not exist (check binaries folder).\n\ -Abandoning bundling process.\n", _app_name.s())); - return 4; - } - - // make sure we snag any keyword that was passed on the command line. - cmds.get_value("keyword", _keyword); - - // first step is to provide some built-in variables that can be used to - // make the manifests less platform specific. this doesn't really help - // if you bundle it on linux and try to run it on windows. but either - // platform's resources can easily be made into a bundle with the same - // packing manifest. -#ifndef __WIN32__ - environment::set("EXE_END", ""); // executable file ending. - environment::set("DLL_START", "lib"); // dll file prefix. - environment::set("DLL_END", ".so"); // dll file ending. -#else - environment::set("EXE_END", ".exe"); - environment::set("DLL_START", ""); - environment::set("DLL_END", ".dll"); -#endif - - int ret = 0; - if ( (ret = read_manifest()) ) FAIL_RETURN(ret, "reading manifest"); - // read manifest to build list of what's what. - if ( (ret = open_output_file()) ) FAIL_RETURN(ret, "opening output file"); - // open up our output file for the bundled chunks. - if ( (ret = write_stub_and_toc()) ) FAIL_RETURN(ret, "writing stub and TOC"); - // writes the stub unpacker application and the table of contents to the - // output file. - if ( (ret = bundle_sources()) ) FAIL_RETURN(ret, "bundling source files"); - // stuff all the source files into the output bundle. - if ( (ret = finalize_file()) ) FAIL_RETURN(ret, "finalizing file"); - // finishes with the file and closes it up. - if ( (ret = write_offset()) ) FAIL_RETURN(ret, "writing offset"); - // stores the offset of the TOC into the output file in a special location - // that is delineated by a known keyword (muftiloc) and which should only - // exist in the file in one location. - - return 0; -} - -int bundle_creator::open_output_file() -{ - FUNCDEF("open_output_file"); - _bundle = new byte_filer(_output_file, "wb"); - if (!_bundle->good()) { - LOG(astring("failed to open the output file: ") + _output_file); - return 65; - } - return 0; -} - -bool bundle_creator::get_file_size(const astring &infile, un_int &size, - byte_array &time_stamp) -{ - FUNCDEF("get_file_size"); - time_stamp.reset(); - // access the source file to get its size. - byte_filer source_file(infile, "rb"); - if (!source_file.good()) { - LOG(astring("could not access the file for size check: ") + infile); - return false; - } - size = int(source_file.length()); - file_time tim(infile); - tim.pack(time_stamp); - return true; -} - -int bundle_creator::add_files_here(directory &dirndl, const astring &source, - const astring &target, int manifest_index) -{ - FUNCDEF("add_files_here"); - for (int i = 0; i < dirndl.files().length(); i++) { - astring curry = dirndl.files()[i]; - // skip .svn folders and contents. - if (curry.contains(SUBVERSION_FOLDER)) continue; -//hmmm: this could be a much nicer generalized file exclusion list. - -//LOG(astring("file is: ") + curry); - bundled_chunk new_guy; - new_guy._source = source + "/" + curry; // the original full path to it. - new_guy._payload = target + "/" + curry; - new_guy._keywords = _manifest_list[manifest_index]._keywords; - // copy the flags from the parent, so we don't forget options. - new_guy._flags = _manifest_list[manifest_index]._flags; - // remove some flags that make no sense for the new guy. - new_guy._flags &= ~RECURSIVE_SRC; - -//LOG(a_sprintf("adding: source=%s targ=%s", new_guy._source.s(), new_guy._payload.s())); - bool okaysize = get_file_size(new_guy._source, new_guy._size, new_guy.c_filetime); - if (!okaysize || (new_guy._size < 0) ) { - LOG(astring("failed to get file size for ") + new_guy._source); - return 75; - } - - _manifest_list.insert(manifest_index + 1, 1); - _manifest_list[manifest_index + 1] = new_guy; - } - return 0; -} - -int bundle_creator::recurse_into_dir(const astring &source, - const astring &target, int manifest_index) -{ -// FUNCDEF("recurse_into_dir"); -//LOG(astring("src=") + source + " dest=" + target); - - // we won't include the subversion folder. - if (source.contains(SUBVERSION_FOLDER)) return 0; - - string_array dirs; // culled from the directory listing. - { - // don't pay for the directory object on the recursive invocation stack; - // just have what we need on the stack (the directory list). - directory dirndl(source); -//check dir for goodness! - int ret = add_files_here(dirndl, source, target, manifest_index); - // add in just the files that were found. - if (ret != 0) { - // this is a failure, but the function complains about it already. - return 75; - } - dirs = dirndl.directories(); - } - -//LOG("now scanning directories..."); - - // now scan across the directories we found. - for (int i = 0; i < dirs.length(); i++) { - astring s = dirs[i]; -//LOG(astring("curr dir is ") + s); - int ret = recurse_into_dir(source + "/" + s, target + "/" - + s, manifest_index); - if (ret != 0) return ret; // bail out. - } - - return 0; -} - -int bundle_creator::patch_recursive_target(const astring &source, - const astring &target, int manifest_index) -{ -// FUNCDEF("patch_recursive_target"); -//LOG(astring("patch recurs src=") + source + " targ=" + target); - return recurse_into_dir(source, target, manifest_index); -} - -int bundle_creator::patch_wildcard_target(const astring &source, - const astring &target, int manifest_index) -{ -// FUNCDEF("patch_wildcard_target"); - // find the last slash. the rest is our wildcard component. - int src_end = source.end(); - int slash_indy = source.find('/', src_end, true); - astring real_source = source.substring(0, slash_indy - 1); - astring wild_pat = source.substring(slash_indy + 1, src_end); -//BASE_LOG(astring("got src=") + real_source + " wildpat=" + wild_pat); - - directory dirndl(real_source, wild_pat.s()); -//check dir for goodness! - int ret = add_files_here(dirndl, real_source, target, manifest_index); - if (ret != 0) { - // this is a failure, but the function complains about it already. - return 75; - } - - return 0; -} - -int bundle_creator::read_manifest() -{ - FUNCDEF("read_manifest"); - ini_configurator ini(_manifest_file, configurator::RETURN_ONLY); - string_table toc; - bool worked = ini.get_section("toc", toc); - if (!worked) { - LOG(astring("failed to read TOC section in manifest:\n") + _manifest_file - + "\ndoes that file exist?"); - return 65; - } - -//hmmm: make a class member. - file_logger noisy_logfile(application_configuration::make_logfile_name - ("bundle_creator_activity.log")); - noisy_logfile.log(astring('-', 76)); - noisy_logfile.log(astring("Bundling starts at ") + time_stamp::notarize(false)); - - // add enough items in the list for our number of sections. - _manifest_list.insert(0, toc.symbols()); - astring value; // temporary string used below. - int final_return = 0; // if non-zero, an error occurred. - -#define BAIL(retval) \ - final_return = retval; \ - toc.zap_index(i); \ - _manifest_list.zap(i, i); \ - i--; \ - continue - - for (int i = 0; i < toc.symbols(); i++) { - // read all the info in this section and store it into our list. - astring section_name = toc.name(i); - section_name.strip_spaces(astring::FROM_FRONT); - if (section_name[0] == '#') { -//hmmm: this looks a bit familiar from bail macro above. abstract out? - toc.zap_index(i); - _manifest_list.zap(i, i); - i--; - continue; // skip comments. - } - - // check for any keywords on the section. these are still needed for - // variables, which otherwise would skip the rest of the field checks. - if (ini.get(section_name, "keyword", value)) { -///LOG(astring("into keyword processing--value held is ") + value); - string_array keys; - bool worked = list_parsing::parse_csv_line(value, keys); - if (!worked) { - LOG(astring("failed to parse keywords for section ") - + section_name + " in " + _manifest_file); - BAIL(82); - } -///LOG(astring("parsed list is ") + keys.text_form()); - _manifest_list[i]._keywords = keys; - astring dumped; - list_parsing::create_csv_line(_manifest_list[i]._keywords, dumped); - noisy_logfile.log(section_name + " keywords: " + dumped); - } - - if (ini.get(section_name, "variable", value)) { - // this is a variable assignment. it is the only thing we care about - // for this section, so the rest is ignored. - variable_tokenizer zohre; - zohre.parse(value); - if (zohre.symbols() < 1) { - LOG(astring("failed to parse a variable statement from ") + value); - BAIL(37); - } - _manifest_list[i]._flags = SET_VARIABLE; // not orred, just this. - // set the two parts of our variable. - _manifest_list[i]._payload = zohre.table().name(0); - _manifest_list[i]._parms = zohre.table()[0]; - BASE_LOG(astring("will set ") + _manifest_list[i]._payload + " = " - + _manifest_list[i]._parms); - astring new_value = parser_bits::substitute_env_vars(_manifest_list[i]._parms); - environment::set(_manifest_list[i]._payload, new_value); - -#ifdef DEBUG_BUNDLER - BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " should have value=" + new_value); - BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " now does have value=" + environment::get(_manifest_list[i]._payload)); -#endif - - continue; - } else if (ini.get(section_name, "assert_defined", value)) { - // they are just asking for a variable test, to see if a variable - // that the installer needs is actually defined at unpacking time. - _manifest_list[i]._payload = value; - _manifest_list[i]._flags = TEST_VARIABLE_DEFINED; - BASE_LOG(astring("will test ") + _manifest_list[i]._payload + " is " - + "defined at unpacking time."); - continue; - } - - if (!ini.get(section_name, "source", _manifest_list[i]._source)) { - // check whether they told us not to pack and it's executable. - bool okay_to_omit_source = false; - astring value2; - if (ini.get(section_name, "no_pack", value) - && ini.get(section_name, "exec_target", value2) ) { - if (true_value(value) && true_value(value2)) { - // this type of section doesn't need source declared. - okay_to_omit_source = true; - } - } - if (!okay_to_omit_source) { - LOG(astring("failed to read the source entry for section ") - + section_name + " in " + _manifest_file); - BAIL(67); - } - } - // fix meshugener backslashes so we can count on the slash direction. - _manifest_list[i]._source.replace_all('\\', '/'); - - if (!ini.get(section_name, "target", _manifest_list[i]._payload)) { - // check whether they told us not to pack and it's executable. - bool okay_to_omit_target = false; - astring value2; - if (ini.get(section_name, "no_pack", value) - && ini.get(section_name, "exec_source", value2) ) { - if (true_value(value) && true_value(value2)) { - // this type of section doesn't need target declared. - okay_to_omit_target = true; - } - } - if (!okay_to_omit_target) { - LOG(astring("failed to read the target entry for section ") - + section_name + " in " + _manifest_file); - BAIL(68); - } - } - // fix backslashes in target also. - _manifest_list[i]._payload.replace_all('\\', '/'); - - // capture any parameters they have specified for exec or other options. - if (ini.get(section_name, "parms", value)) { - _manifest_list[i]._parms = value; -#ifdef DEBUG_BUNDLER - BASE_LOG(astring("got parms for ") + section_name + " as: " + value); -#endif - if (value[0] != '"') { - // repair the string if we're running on windows. - _manifest_list[i]._parms = astring("\"") + value + "\""; - } - noisy_logfile.log(section_name + " parms: " + _manifest_list[i]._parms); - } - - // check for the ignore errors flag. - if (ini.get(section_name, "error_okay", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= IGNORE_ERRORS; - } - - // see if they are saying not to overwrite the target file. - if (ini.get(section_name, "no_replace", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= NO_OVERWRITE; - } - - // test whether they are saying not to complain about a failure with - // our normal pop-up dialog (on winders). - if (ini.get(section_name, "quiet", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= QUIET_FAILURE; - } - - // did they want a backup of the original to be made, instead of - // just overwriting the file? - if (ini.get(section_name, "make_backup", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= MAKE_BACKUP_FILE; - } - - // look for our recursion flag. - if (ini.get(section_name, "recurse", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= RECURSIVE_SRC; - } else { - // the options here are only appropriate when the target is NOT set to - // be recursive. - - if (ini.get(section_name, "no_pack", value)) { - // allow either side to not be required if this is an executable. - if (true_value(value)) - _manifest_list[i]._flags |= OMIT_PACKING; - } - - // check if they have specified a source side executable. - if (ini.get(section_name, "exec_source", value)) { - if (true_value(value)) { - _manifest_list[i]._flags |= SOURCE_EXECUTE; - } - } else { - // check if they have specified a target side executable. this is - // mutually exclusive with a source side exec. - if (ini.get(section_name, "exec_target", value)) { - if (true_value(value)) - _manifest_list[i]._flags |= TARGET_EXECUTE; - } - } - } - - // replace environment variables in the source now... - _manifest_list[i]._source = parser_bits::substitute_env_vars - (_manifest_list[i]._source, false); - - // look for wildcards in the source. - int indy = _manifest_list[i]._source.find("*"); - - // see if they specified a keyword on the command line and if this matches. - // if not we need to abandon this item. - if (!!_keyword && !_manifest_list[i]._keywords.member(_keyword)) { - // their keyword choice didn't match what we were told to use. - noisy_logfile.log(astring("skipping ") + _manifest_list[i]._payload - + " file check; doesn't match keyword \"" + _keyword + "\""); - continue; - } - - // we only access the source file here if it's finalized. we can't do - // this if the target is supposed to be recursive or if it's got a wildcard - // pattern in it. - if (!(_manifest_list[i]._flags & RECURSIVE_SRC) && negative(indy) - && !(_manifest_list[i]._flags & OMIT_PACKING) ) { - // access the source file to get its size. - byte_filer source_file(_manifest_list[i]._source, "rb"); - if (!source_file.good()) { - LOG(astring("could not access the source file for bundling: ") - + _manifest_list[i]._source); - BAIL(69); - } - bool okaysize = get_file_size(_manifest_list[i]._source, - _manifest_list[i]._size, _manifest_list[i].c_filetime); - if (!okaysize || (_manifest_list[i]._size < 0) ) { - // this is a failure, but the function complains about it already. - BAIL(75); - } - } - } - - // patch the manifest list for wildcards and recursive sources. - for (int i = 0; i < _manifest_list.length(); i++) { - bundled_chunk curr = _manifest_list[i]; - - if (!!_keyword && !curr._keywords.member(_keyword)) { - // this item's keyword doesn't match the one we were given, so skip it. - noisy_logfile.log(astring("zapping entry for ") + curr._payload - + "; doesn't match keyword \"" + _keyword + "\""); - _manifest_list.zap(i, i); - i--; // skip back since we eliminated an index. - continue; - } - - if (curr._flags & SET_VARIABLE) { - // we're done working on this. - continue; - } else if (curr._flags & TEST_VARIABLE_DEFINED) { - // this also requires no further effort. - continue; - } else if (curr._flags & RECURSIVE_SRC) { - // handle a recursive style target. - int star_indy = curr._source.find("*"); - if (non_negative(star_indy)) { - // this is currently illegal. we don't allow recursion + wildcards. - LOG(astring("illegal combination of recursion and wildcard: ") - + curr._source); - BAIL(70); - } - // handle the recursive guy. - int ret = patch_recursive_target(curr._source, curr._payload, i); - if (ret != 0) { - LOG(astring("failed during packing of recursive source: ") - + curr._source); - BAIL(72); - } - // take this item out of the picture, since all contents got included. - _manifest_list.zap(i, i); - i--; // skip back since we eliminated an index. - continue; - } else if (curr._flags & SOURCE_EXECUTE) { - // we have massaged the current manifest chunk as much as we can, so now - // we will execute the source item if that was specified. - BASE_LOG(astring("launching ") + curr._source); - if (!!curr._parms) { - curr._parms = parser_bits::substitute_env_vars(curr._parms, false); - BASE_LOG(astring("\tparameters ") + curr._parms); - } - BASE_LOG(astring('-', 76)); - basis::un_int kid; - basis::un_int retval = launch_process::run(curr._source, curr._parms, - launch_process::AWAIT_APP_EXIT, kid); - if (retval != 0) { - LOG(astring("failed to launch process, source=") + curr._source - + ", with parms " + curr._parms); - if (! (curr._flags & IGNORE_ERRORS) ) { - BAIL(92); - } - } - BASE_LOG(astring('-', 76)); - if (curr._flags & OMIT_PACKING) { - // this one shouldn't be included in the package. - _manifest_list.zap(i, i); - i--; // skip back since we eliminated an index. - } - continue; - } else { - // check for a wildcard. - int star_indy = curr._source.find("*"); - if (negative(star_indy)) continue; // simple targets are boring. - // this does have a wildcard in it. let's make sure it's in the right - // place for a wildcard in our scheme. - int slash_indy = curr._source.find('/', curr._source.end(), true); - if (star_indy < slash_indy) { - BASE_LOG(astring("illegal wildcard placement in ") + curr._source); - BASE_LOG(astring(" (the wildcard must be in the last component of the path)")); - BAIL(71); - } - // handle the wildcarded source. - int ret = patch_wildcard_target(curr._source, curr._payload, i); - if (ret != 0) { - LOG(astring("failed during packing of wildcarded source: ") - + curr._source); - BAIL(73); - } - _manifest_list.zap(i, i); - i--; // skip back since we eliminated an index. - continue; - } - } - -#ifdef DEBUG_BUNDLER - if (!final_return) { - // we had a successful run so we can print this stuff out. - LOG("read the following info from manifest:"); - for (int i = 0; i < _manifest_list.length(); i++) { - bundled_chunk &curr = _manifest_list[i]; - BASE_LOG(a_sprintf("(%d) size %d, %s => %s", i, curr._size, - curr._source.s(), curr._payload.s())); - } - } -#endif - - return final_return; -} - -astring bundle_creator::determine_stub_file_and_validate() -{ - FUNCDEF("determine_stub_file_and_validate"); - // define our location to find the unpacking stub program. -//hmmm: make this a command line parameter. -#ifdef __UNIX__ - astring stub_filename("unpacker_stub"); -#endif -#ifdef __WIN32__ - astring stub_filename("unpacker_stub.exe"); -#endif - astring repo_dir = "$PRODUCTION_DIR"; - astring stub_file = parser_bits::substitute_env_vars - (repo_dir + "/binaries/" + stub_filename, false); - if (!filename(stub_file).exists()) { - // we needed to find that to build the bundle. - LOG(astring("could not find unpacking stub file at: ") + stub_file); - return astring::empty_string(); - } - return stub_file; -} - -int bundle_creator::write_stub_and_toc() -{ - FUNCDEF("write_stub_and_toc"); - - astring stub_file = determine_stub_file_and_validate(); - if (!stub_file) return 1; - - // make sure the stub is accessible. - byte_filer stubby(stub_file, "rb"); - if (!stubby.good()) { - FAIL_RETURN(80, astring("could not read the unpacking stub at: ") + stub_file); - } - _stub_size = int(stubby.length()); // get the stub size for later reference. - byte_array whole_stub; - stubby.read(whole_stub, _stub_size + 100); - stubby.close(); - _bundle->write(whole_stub); - - byte_array packed_toc_len; - structures::obscure_attach(packed_toc_len, _manifest_list.length()); - int ret = _bundle->write(packed_toc_len); - if (ret < 0) { - LOG(astring("could not write the TOC length to the bundle: ") - + _output_file); - return 81; - } - - // dump out the manifest list in our defined format. - for (int i = 0; i < _manifest_list.length(); i++) { - bundled_chunk &curr = _manifest_list[i]; -//LOG(a_sprintf("flag %d is %d", i, curr._flags)); - byte_array chunk; - curr.pack(chunk); - if (_bundle->write(chunk) <= 0) { - LOG(a_sprintf("could not write item #%d [%s] to the bundle: ", i, - curr._source.s()) - + _output_file); - return 88; - } - } - - return 0; -} - -int bundle_creator::bundle_sources() -{ - FUNCDEF("bundle_sources"); - // go through all the source files and append them to the bundled output. - file_logger noisy_logfile(application_configuration::make_logfile_name - ("bundle_creator_activity.log")); - for (int i = 0; i < _manifest_list.length(); i++) { - bundled_chunk &curr = _manifest_list[i]; - - if (curr._flags & SET_VARIABLE) { - // all we need to do is keep this in the manifest. - noisy_logfile.log(astring("bundling: variable setting ") + curr._payload - + "=" + curr._parms); - continue; - } else if (curr._flags & TEST_VARIABLE_DEFINED) { - // just remember to test this when running the unpack. - noisy_logfile.log(astring("bundling: test variable ") + curr._payload - + " is defined."); - continue; - } else if (curr._flags & OMIT_PACKING) { - // this one shouldn't be included in the package. - continue; - } - - noisy_logfile.log(astring("bundling: ") + curr._source); - byte_filer source(curr._source, "rb"); - if (!source.good()) { - LOG(a_sprintf("could not read item #%d for the bundle: \"", i) - + curr._source + "\""); - return 98; - } - - byte_array compressed(256 * KILOBYTE); // expand the buffer to start with. - byte_array temp; // temporary read buffer. - - // chew on the file a chunk at a time. this allows us to easily handle - // arbitrarily large files rather than reading their entirety into memory. - int total_written = 0; - do { - int ret = source.read(temp, CHUNKING_SIZE); - if (ret < 0) { - LOG(a_sprintf("failed while reading item #%d: ", i) + curr._source); - return 99; - } - total_written += ret; // add in what we expect to write. - // skip compressing if there's no data. - uLongf destlen = 0; - bool null_chunk = false; - if (ret == 0) { - compressed.reset(); - null_chunk = true; - } else { - compressed.reset(int(0.1 * ret) + ret + KILOBYTE); - // provide some extra space as per zlib instructions. we're giving it - // way more than they request. - destlen = compressed.length(); - // pack the chunks first so we can know sizes needed. - int comp_ret = compress(compressed.access(), &destlen, temp.observe(), - temp.length()); - if (comp_ret != Z_OK) { - LOG(a_sprintf("failed while compressing item #%d: ", i) - + curr._source); - return 99; - } - compressed.zap(destlen, compressed.length() - 1); - } - byte_array just_sizes; - structures::obscure_attach(just_sizes, temp.length()); - // add in the real size. - structures::obscure_attach(just_sizes, int(destlen)); - // add in the packed size. - ret = _bundle->write(just_sizes); - if (ret <= 0) { - LOG(a_sprintf("failed while writing sizes for item #%d: ", i) - + curr._source); - return 93; - } - if (!null_chunk) { - ret = _bundle->write(compressed); - if (ret <= 0) { - LOG(a_sprintf("failed while writing item #%d: ", i) + curr._source); - return 93; - } else if (ret != compressed.length()) { - LOG(a_sprintf("wrote different size for item #%d (tried %d, " - "wrote %d): ", i, compressed.length(), ret) + curr._source); - return 93; - } - } - } while (!source.eof()); -//hmmm: very common code to above size writing. - byte_array just_sizes; - structures::obscure_attach(just_sizes, -1); - structures::obscure_attach(just_sizes, -1); - int ret = _bundle->write(just_sizes); - if (ret <= 0) { - LOG(a_sprintf("failed while writing sentinel of item #%d: ", i) - + curr._source); - return 96; - } - source.close(); - if (total_written != curr._size) { - LOG(a_sprintf("size (%d) disagrees with initial size (%d) for " - "item #%d: ", total_written, curr._size, i) + curr._source); - } - } - noisy_logfile.log(astring("Bundling run ends at ") + time_stamp::notarize(false)); - noisy_logfile.log(astring('-', 76)); - - return 0; -} - -int bundle_creator::finalize_file() -{ - _bundle->close(); - return 0; -} - -int bundle_creator::write_offset() -{ -// FUNCDEF("write_offset"); - byte_filer bun(_output_file, "r+b"); // open the file for updating. - - astring magic_string("muftiloc"); // our sentinel string. - astring temp_string; // data from the file. - - while (!bun.eof()) { - // find the telltale text in the file. - bool found_it = false; // we'll set this to true if we see the string. - int location = 0; // where the sentinel's end is. - for (int i = 0; i < magic_string.length(); i++) { - int ret = bun.read(temp_string, 1); - if (ret <= 0) break; - if (temp_string[0] != magic_string[i]) break; // no match. - if (i == magic_string.end()) { - // we found a match to our string! - found_it = true; - location = int(bun.tell()); -//LOG(a_sprintf("found the sentinel in the file! posn=%d", location)); - } - } - if (!found_it) continue; // keep reading. - bun.seek(location); - byte_array packed_offset; - structures::obscure_attach(packed_offset, _stub_size); -//LOG(astring("pattern of len is:\n") + byte_format::text_dump(packed_offset)); - // write the offset into the current position, which should be just after - // the sentinel's location. - bun.write(packed_offset); -//LOG(a_sprintf("wrote manifest offset before posn=%d", bun.tell())); - break; // done with looking for that pattern. - } - bun.close(); // completely finished now. - - chmod(_output_file.s(), 0766); - // make sure it's an executable file when we're done with it. - - BASE_LOG(astring("done file bundling at ") + time_stamp::notarize(false)); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(bundle_creator, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/applications/bundler/common_bundle.cpp b/core/applications/bundler/common_bundle.cpp deleted file mode 100644 index 6d170b59..00000000 --- a/core/applications/bundler/common_bundle.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/*****************************************************************************\ -* * -* Name : common bundler definitions * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "common_bundle.h" - -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace filesystem; -using namespace structures; - -manifest_chunk::~manifest_chunk() -{} - -int manifest_chunk::packed_filetime_size() -{ - static file_time hidden_comparison_object; - return hidden_comparison_object.packed_size(); -} - -void manifest_chunk::pack(byte_array &target) const -{ - structures::obscure_attach(target, _size); - _payload.pack(target); - structures::attach(target, _flags); - _parms.pack(target); - _keywords.pack(target); - target += c_filetime; -} - -bool manifest_chunk::unpack(byte_array &source) -{ - if (!structures::obscure_detach(source, _size)) return false; - if (!_payload.unpack(source)) return false; - if (!structures::detach(source, _flags)) return false; - if (!_parms.unpack(source)) return false; - if (!_keywords.unpack(source)) return false; - if (source.length() < 8) return false; - c_filetime = source.subarray(0, 7); - source.zap(0, 7); - return true; -} - -bool manifest_chunk::read_an_int(byte_filer &bundle, un_int &found) -{ -// FUNCDEF("read_an_int"); - byte_array temp; - if (bundle.read(temp, sizeof(int)) != sizeof(int)) return false; - if (!structures::detach(temp, found)) return false; - return true; -} - -bool manifest_chunk::read_an_obscured_int(byte_filer &bundle, un_int &found) -{ -// FUNCDEF("read_an_obscured_int"); - byte_array temp; - if (bundle.read(temp, 2 * sizeof(int)) != 2 * sizeof(int)) return false; - if (!structures::obscure_detach(temp, found)) return false; - return true; -} - -bool manifest_chunk::read_a_filetime(byte_filer &bundle, byte_array &found) -{ -// FUNCDEF("read_a_filetime"); - byte_array temp; - // the trick below only works because we know we have a constant sized packed version - // for the file time. - if (bundle.read(temp, packed_filetime_size()) != packed_filetime_size()) return false; - found = temp; - return true; -} - -astring manifest_chunk::read_a_string(byte_filer &bundle) -{ -// FUNCDEF("read_a_string"); - astring found; - byte_array temp; - // read in the zero-terminated character string. - while (!bundle.eof()) { - // read a single byte out of the file. - if (bundle.read(temp, 1) <= 0) - break; - if (temp[0]) { - // add the byte to the string we're accumulating. - found += temp[0]; - } else { - // this string is done now. - break; - } - } - return found; -} - -bool manifest_chunk::read_manifest(byte_filer &bundle, manifest_chunk &curr) -{ - curr._size = 0; - bool worked = read_an_obscured_int(bundle, curr._size); - if (!worked) - return false; - byte_array temp; - curr._payload = read_a_string(bundle); - if (!curr._payload) return false; - worked = read_an_int(bundle, curr._flags); - if (!worked) - return false; - curr._parms = read_a_string(bundle); - // it's valid for the _parms to be empty. -//if (curr._parms.length()) { printf("parms len=%d are: \"%s\"\n", curr._parms.length(), curr._parms.s()); } - // now get the keywords list, if it exists. - un_int key_elems = 0; // number of keywords. - worked = read_an_obscured_int(bundle, key_elems); // get number of elements. - if (!worked) - return false; - curr._keywords.reset(); - for (int i = 0; i < (int)key_elems; i++) { - astring found = read_a_string(bundle); - if (!found) return false; // not allowed an empty keyword. - curr._keywords += found; - } - worked = read_a_filetime(bundle, curr.c_filetime); - return worked; -} - diff --git a/core/applications/bundler/common_bundle.h b/core/applications/bundler/common_bundle.h deleted file mode 100644 index 9e9a57e3..00000000 --- a/core/applications/bundler/common_bundle.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef COMMON_BUNDLER_DEFS -#define COMMON_BUNDLER_DEFS - -/*****************************************************************************\ -* * -* Name : common bundler definitions * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! Contains some definitions used by both the bundle creator and unpacker. -/*! - Note that this is a heavyweight header and should not be pulled into - other headers. -*/ - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////// - -//! flags that control special attributes of the packed files. -enum special_bundling_flags { - SOURCE_EXECUTE = 0x2, //!< the file should be executed before bundling. - TARGET_EXECUTE = 0x4, //!< the file should be executed on unbundling. - RECURSIVE_SRC = 0x8, //!< source is a recursive folder. - OMIT_PACKING = 0x10, //!< for a source side exe, do not pack the file. - SET_VARIABLE = 0x20, //!< this item just has a variable assignment. - IGNORE_ERRORS = 0x40, //!< if set, errors in an item will not stop program. - NO_OVERWRITE = 0x80, //!< target file will not be overwritten if exists. - QUIET_FAILURE = 0x100, //!< when errors happen, no popup message happens. - MAKE_BACKUP_FILE = 0x200, //!< save a copy if original file already exists. - TEST_VARIABLE_DEFINED = 0x400 //!< check for required variable's presence. -}; - -//////////////////////////////////////////////////////////////////////////// - -//! we will read the manifest pieces out of our own exe image. -/*! - the manifest chunks provide us with enough information to unpack the - data chunks that come afterward. -*/ - -struct manifest_chunk : public basis::text_formable -{ - basis::un_int _size; //!< the size of the packed file. - basis::astring _payload; //!< guts of the chunk, such as location for file on target or a variable definition. - basis::un_int _flags; //!< uses the special_bundling_flags. - basis::astring _parms; //!< the parameters to pass on the command line. - structures::string_set _keywords; //!< keywords applicable to this item. - basis::byte_array c_filetime; //!< more than enough room for unix file time. - - // note: when flags has SET_VARIABLE, the _payload is the variable - // name to be set and the _parms is the value to use. - - static int packed_filetime_size(); - - //! the chunk is the unit found in the packing manifest in the bundle. - manifest_chunk(int size, const basis::astring &target, int flags, - const basis::astring &parms, const structures::string_set &keywords) - : _size(size), _payload(target), _flags(flags), _parms(parms), - _keywords(keywords), c_filetime(packed_filetime_size()) { - for (int i = 0; i < packed_filetime_size(); i++) c_filetime[i] = 0; - } - - manifest_chunk() : _size(0), _flags(0), c_filetime(packed_filetime_size()) { - //!< default constructor. - for (int i = 0; i < packed_filetime_size(); i++) c_filetime[i] = 0; - } - - virtual ~manifest_chunk(); - - virtual void text_form(basis::base_string &state_fill) const { - state_fill.assign(basis::astring(class_name()) - + basis::a_sprintf(": size=%d payload=%s flags=%x parms=%s", - _size, _payload.s(), _flags, _parms.s())); - } - - DEFINE_CLASS_NAME("manifest_chunk"); - - void pack(basis::byte_array &target) const; //!< streams out into the "target". - bool unpack(basis::byte_array &source); //!< streams in from the "source". - - static bool read_manifest(filesystem::byte_filer &bundle, manifest_chunk &to_fill); - //!< reads a chunk out of the "bundle" and stores it in "to_fill". - /*!< false is returned if the read failed. */ - - static basis::astring read_a_string(filesystem::byte_filer &bundle); - //!< reads a string from the "bundle" file, one byte at a time. - - static bool read_an_int(filesystem::byte_filer &bundle, basis::un_int &found); - //!< reads an integer (4 bytes) from the file into "found". - - static bool read_an_obscured_int(filesystem::byte_filer &bundle, basis::un_int &found); - //!< reads in our obscured packing format for an int, which takes 8 bytes. - - static bool read_a_filetime(filesystem::byte_filer &bundle, basis::byte_array &found); - //!< retrieves packed_filetime_size() byte timestamp from the "bundle". -}; - -//////////////////////////////////////////////////////////////////////////// - -#endif - diff --git a/core/applications/bundler/makefile b/core/applications/bundler/makefile deleted file mode 100644 index 1137f980..00000000 --- a/core/applications/bundler/makefile +++ /dev/null @@ -1,27 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -PROJECT = app_bundle -TYPE = application -SOURCE = common_bundle.cpp -ifeq "$(OMIT_VERSIONS)" "" - SOURCE += bundler_version.rc -endif -DEFINITIONS += __BUILD_STATIC_APPLICATION__=t -TARGETS = bundle_creator.exe -LAST_TARGETS += make_stub -ifeq "$(OP_SYSTEM)" "WIN32" - LOCAL_HEADERS += $(THIRD_PARTY_DIR)/zlib/include - LOCAL_LIBRARIES += $(THIRD_PARTY_DIR)/zlib/lib - LIBS_USED += zlib.lib -endif -ifeq "$(OP_SYSTEM)" "UNIX" - LIBS_USED += z -endif - -include cpp/rules.def - -make_stub: - $(MAKE) -f makefile.stub - diff --git a/core/applications/bundler/makefile.stub b/core/applications/bundler/makefile.stub deleted file mode 100644 index 4ab4683e..00000000 --- a/core/applications/bundler/makefile.stub +++ /dev/null @@ -1,24 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -PROJECT = app_bundle_stub -TYPE = application -SOURCE = common_bundle.cpp -ifeq "$(OMIT_VERSIONS)" "" - SOURCE += bundler_version.rc -endif -DEFINITIONS += __BUILD_STATIC_APPLICATION__=t -TARGETS = unpacker_stub.exe -ifeq "$(OP_SYSTEM)" "WIN32" - LIBS_USED += libcmt.lib shlwapi.lib zlib.lib - LOAD_FLAG_PREFIX += -nodefaultlib:msvcrt.lib - COMPILER_FLAGS += -MT - LOCAL_HEADERS += $(THIRD_PARTY_DIR)/zlib/include - LOCAL_LIBRARIES += $(THIRD_PARTY_DIR)/zlib/lib -else - LIBS_USED += z -endif - -include cpp/rules.def - diff --git a/core/applications/bundler/manifest_format.txt b/core/applications/bundler/manifest_format.txt deleted file mode 100644 index 20e38a93..00000000 --- a/core/applications/bundler/manifest_format.txt +++ /dev/null @@ -1,23 +0,0 @@ - -the unpacking manifest is a structure defined in terms of bytes. -the exe's manifest offset is set to point to the beginning of this structure. - -bytes content ------ ------- -0 => 3 number of chunks in the TOC -4 => 4+N-1 first manifest item, with length N -4+N => 4+N+M-1 second item, with length M -4+N+M =>...etc. - -each bundle chunk has a structure: - -bytes content ------ ------- -0 => 3 size of the data component of the chunk -4 => 4+S-1 the file system target location for this chunk, as a zero - terminated string (of length S). this string comes from the - target defined in the packing manifest. - -then the data starts after the end of the TOC; each chunk occupies the space -declared for it in the manifest. - diff --git a/core/applications/bundler/unpacker_stub.cpp b/core/applications/bundler/unpacker_stub.cpp deleted file mode 100644 index a515d8cc..00000000 --- a/core/applications/bundler/unpacker_stub.cpp +++ /dev/null @@ -1,660 +0,0 @@ -/*****************************************************************************\ -* * -* Name : unpacker stub program * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "common_bundle.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef __UNIX__ - #include -#endif -#ifdef __WIN32__ - #include - #include - #include - #include -#endif - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; -using namespace textual; - -const int CHUNKING_SIZE = 64 * KILOBYTE; - // we'll read this big a chunk from a source file at a time. - -const astring TARGET_WORD = "TARGET"; -const astring LOGDIR_WORD = "LOGDIR"; - -#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) -#define LOG(to_print) STAMPED_EMERGENCY_LOG(program_wide_logger::get(), to_print) - -//#define DEBUG_STUB - // uncomment for noisier version. - -const char *ERROR_TITLE = "An Error Caused Incomplete Installation"; - // used in error messages as the title. - -//////////////////////////////////////////////////////////////////////////// - -class unpacker_stub : public application_shell -{ -public: - unpacker_stub() : application_shell(), _app_name(filename(_global_argv[0]).basename()) {} - DEFINE_CLASS_NAME("unpacker_stub"); - - int print_instructions(); - - virtual int execute(); - -private: - astring _app_name; - array _manifest; //!< the list of chunks to unpack. - string_table _variables; //!< our list of variable overrides. -}; - -//////////////////////////////////////////////////////////////////////////// - -void show_message(const astring &msg, const astring &title) -{ -#ifndef __WIN32__ - BASE_LOG(title); - BASE_LOG(astring('-', title.length())); - BASE_LOG(msg); -#else - MessageBox(0, to_unicode_temp(msg), to_unicode_temp(title), - MB_OK | MB_ICONINFORMATION); -#endif -} - -int unpacker_stub::print_instructions() -{ - a_sprintf msg("\ - %s: This program unpacks its contents into the locations\n\ - specified at packing time. The --target flag can be used to specify a\n\ -different %s directory for the installation (for components that use\n\ -the default %s variable to specify their install folder).\n\ - One can also pass a --keyword flag to specify a keyword; the files in the\n\ -bundle that are marked with that keyword will be installed, but files that\n\ -are missing the keyword will not be.\n\ - Further, variables can be overridden on the command line in the\n\ -form: X=Y.\n\n\ -The line below uses all these parameters as an example:\n\n\ - %s --target c:\\Program Files\\gubernator --keyword dlls_only SILENT=true\n\ -\n\ -Additional Notes:\n\ -\n\ - One helpful variable is \"%s\". This is where the unpacking log file\n\ -will be written to. The default is \"~/logs\" (or \"$TMP/logs\" on win32)\n\ -until overridden.\n\ -\n", _app_name.s(), TARGET_WORD.s(), TARGET_WORD.s(), _app_name.s(), LOGDIR_WORD.s()); - show_message(msg, "Unpacking Instructions"); - return 12; -} - -// creates a unique backup file name, if it can. -// we assume that this file already exists, but we want to check for -// our backup file naming scheme in case we already backed this up -// some previous time. -astring find_unique_backup_name(astring original_file) -{ -const int MAXIMUM_BACKUPS = 200; - - for (int i = 0; i < MAXIMUM_BACKUPS; i++) { - filename target_file = original_file + a_sprintf(".%04d", i); - if (target_file.exists()) { -//BASE_LOG(astring("bkup already here: ") + target_file); - continue; - } - // this file is okay to use. - return target_file; - } - return ""; // nothing found. -} - - -// the string embedded into the array is not mentioned anywhere else in this -// program, which should allow the packer to find it and fix the manifest -// size. the keyword is: "muftiloc", and the first bytes that can be -// overwritten are its beginning offset plus its length of 8 chars. there -// is room for 8 bytes after the tag, but currently the first 4 are used as -// a 32 bit offset. -abyte MANIFEST_OFFSET_ARRAY[] - = { 'm', 'u', 'f', 't', 'i', 'l', 'o', 'c', 0, 0, 0, 0, 0, 0, 0, 0 }; - -int unpacker_stub::execute() -{ -#ifdef ADMIN_CHECK - #ifdef __WIN32__ - if (IsUserAnAdmin()) { - ::MessageBox(0, to_unicode_temp("IS admin in bundler"), to_unicode_temp("bundler"), MB_OK); - } else { - ::MessageBox(0, to_unicode_temp("NOT admin in bundler"), to_unicode_temp("bundler"), MB_OK); - } - #endif -#endif - - command_line cmds(_global_argc, _global_argv); - - int indy = 0; - if (cmds.find('?', indy)) return print_instructions(); - if (cmds.find("?", indy)) return print_instructions(); - - // make sure we provide the same services as the bundle creator for the - // default set of variables. -#ifndef __WIN32__ - environment::set("EXE_END", ""); // executable file ending. - environment::set("DLL_START", "lib"); // dll file prefix. - environment::set("DLL_END", ".so"); // dll file ending. -#else - environment::set("EXE_END", ".exe"); - environment::set("DLL_START", ""); - environment::set("DLL_END", ".dll"); -#endif - - // set TARGET directory if passed on the command line, - bool provided_target = false; // true if the command line specified target. - astring target; - cmds.get_value("target", target); - if (!target) { -/* no, this is wrong headed. make them supply a target if there is - * no default target provided in the bundle. - // a bogus default is provided if they don't specify the destination. - target = environment::get("TMP") + "/unbundled"; -*/ - provided_target = false; - } else { -//LOG(astring("target is now ") + target); - environment::set(TARGET_WORD, target); - provided_target = true; - } - - { - astring logdir = environment::get(LOGDIR_WORD); -#ifdef __WIN32__ - if (!logdir) { - logdir = environment::get("TMP") + "/logs"; - environment::set(LOGDIR_WORD, logdir); - } -#else - if (!logdir) { - astring homedir = environment::get("HOME"); - logdir = homedir + "/logs"; - environment::set(LOGDIR_WORD, logdir); - } -#endif - } - - astring keyword; // set if we were given a keyword on cmd line. - cmds.get_value("keyword", keyword); - - astring vars_set; // we will document the variables we saw and show later. - - for (int x = 0; x < cmds.entries(); x++) { - command_parameter curr = cmds.get(x); - if (curr.type() != command_parameter::VALUE) continue; // skip it. - if (curr.text().find('=', 0) < 0) continue; // no equals character. - variable_tokenizer t; - t.parse(curr.text()); - if (!t.symbols()) continue; // didn't parse right. - astring var = t.table().name(0); - astring value = t.table()[0]; - vars_set += astring("variable set: ") + var + "=" + value - + parser_bits::platform_eol_to_chars(); - if (var == TARGET_WORD) { - provided_target = true; - } -//hmmm: handle LOGDIR passed as variable this way also! - _variables.add(var, value); - environment::set(var, value); - } - - // get the most up to date version of the variable now. - astring logdir = environment::get(LOGDIR_WORD); - - astring appname = filename(application_configuration::application_name()).rootname(); - - astring logname = logdir + "/" + appname + ".log"; -// log_base *old_log = set_PW_logger_for_combo(logname); - standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname)); - WHACK(old_log); - - BASE_LOG(astring('#', 76)); - BASE_LOG(appname + " command-line parameters:"); - BASE_LOG(cmds.text_form()); - BASE_LOG(astring('#', 76)); - - BASE_LOG(vars_set); - -#ifdef __WIN32__ - // create a window so that installshield won't barf. this is only needed - // on windows when using this as a prerequisite for installshield. - window_handle f_window = create_simplistic_window("temp_stubby_class", - "stubby window title"); -#endif - - // read position for manifest offset out of our array. - byte_array temp_packed(2 * sizeof(int), MANIFEST_OFFSET_ARRAY + 8); - un_int manifest_offset; - if (!structures::obscure_detach(temp_packed, manifest_offset)) { - show_message(astring("could not read manifest offset in: ") + _global_argv[0], - ERROR_TITLE); - return 24; - } - - filename this_exe(_global_argv[0]); - if (!this_exe.exists()) { - show_message(astring("could not access this exe image: ") + this_exe.raw(), - ERROR_TITLE); - return 23; - } - - // start reading the manifest... - byte_filer our_exe(this_exe, "rb"); - our_exe.seek(manifest_offset); // go to where the manifest starts. - - // get number of chunks in manifest as the first bytes. - if (our_exe.read(temp_packed, 2 * sizeof(int)) <= 0) { - show_message(astring("could not read the manifest length in: ") - + this_exe.raw(), ERROR_TITLE); - return 26; - } - un_int item_count; - structures::obscure_detach(temp_packed, item_count); -//check result of detach! - _manifest.insert(0, item_count); // add enough spaces for our item list. - - // read each item definition out of the manifest now. - for (int i = 0; i < (int)item_count; i++) { - manifest_chunk &curr = _manifest[i]; - bool worked = manifest_chunk::read_manifest(our_exe, curr); - -#ifdef DEBUG_STUB - astring tmpork; - curr.text_form(tmpork); - LOG(a_sprintf("item %d: ", i) + tmpork); -#endif - - if (!worked) { - show_message(a_sprintf("could not read chunk for item #%d [%s]: ", i, - curr._payload.s()) - + this_exe.raw(), ERROR_TITLE); - return 86; - } - } - -#ifdef DEBUG_STUB - LOG("read the following info from manifest:"); - astring temp; - for (int i = 0; i < _manifest.length(); i++) { - manifest_chunk &curr = _manifest[i]; - temp += a_sprintf("(%d) size %d, %s\n", i, curr._size, - curr._payload.s()); - } - critical_events::alert_message(temp, "manifest contents"); -#endif - - // now we should be just after the last byte of the manifest, at the - // first piece of data. we should read each chunk of data out and store - // it where it's supposed to go. - for (int festdex = 0; festdex < _manifest.length(); festdex++) { - manifest_chunk &curr = _manifest[festdex]; - int size_left = curr._size; - - // patch in our environment variables. - curr._payload = parser_bits::substitute_env_vars(curr._payload, false); - curr._parms = parser_bits::substitute_env_vars(curr._parms, false); -#ifdef DEBUG_STUB - BASE_LOG(astring("processing ") + curr._payload - + a_sprintf(", size=%d, flags=%d", curr._size, curr._flags)); - if (!!curr._parms) - BASE_LOG(astring(" parms: ") + curr._parms); -#endif - - // see if they specified a keyword on the command line. - bool keyword_good = true; - if (!!keyword && !curr._keywords.member(keyword)) { - // their keyword choice didn't match what we were pickled with. - keyword_good = false; -//BASE_LOG(astring("skipping ") + curr._payload + " for wrong keyword " + keyword); - } - - if (curr._flags & SET_VARIABLE) { - if (keyword_good) { - // this is utterly different from a real target. we just set the - // variable and move on. - if (provided_target && (curr._payload == TARGET_WORD) ) { - BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms - + ": was provided explicitly as " + target); - } else if (_variables.find(curr._payload)) { - BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms - + ": was provided on command line."); - } else { - BASE_LOG(astring("setting ") + curr._payload + "=" + curr._parms); - environment::set(curr._payload, curr._parms); - - // special code for changing logging directory midstream. - if (curr._payload == LOGDIR_WORD) { - astring logdir = curr._parms; - astring appname = filename(application_configuration::application_name()).rootname(); - astring logname = logdir + "/" + appname + ".log"; - standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname)); -/// log_base *old_log = set_PW_logger_for_combo(logname); - WHACK(old_log); - } - if (curr._payload == TARGET_WORD) { - // well we've now seen this defined. - provided_target = true; - } - } - } - continue; - } else if (curr._flags & TEST_VARIABLE_DEFINED) { - if (keyword_good) { - astring var_value = environment::get(curr._payload); - if (var_value.empty()) { - BASE_LOG(astring("assertion failed: ") + curr._payload + " is not defined!"); - show_message(a_sprintf("failed test for variable %s: it is " - "*not* defined, for item #%d.", curr._payload.s(), festdex), - ERROR_TITLE); - return 98; - } - BASE_LOG(astring("assertion succeeded: ") + curr._payload + " defined as " + var_value); - } - continue; - } - - if (! (curr._flags & OMIT_PACKING) ) { - // this one has a payload, so install it now if appropriate. - if (!provided_target) { -//error! - BASE_LOG(astring("No TARGET has been specified; please provide one on the command line.") + parser_bits::platform_eol_to_chars()); - return print_instructions(); - } - - // make sure that the directories needed are present for the outputs. - filename target_dir = filename(curr._payload).dirname(); - - if (keyword_good && !target_dir.exists() - && !directory::recursive_create(target_dir)) { - LOG(a_sprintf("failed to create directory %s for item #%d: ", - target_dir.raw().s(), festdex) + curr._payload); - } - - // test whether they wanted to allow overwriting. - if (curr._flags & NO_OVERWRITE) { - filename target_file(curr._payload); - if (target_file.exists()) { - BASE_LOG(astring("not overwriting existing ") + curr._payload); - keyword_good = false; - } - } - - // see if this is supposed to be backed up before installation. - if (curr._flags & MAKE_BACKUP_FILE) { - filename target_file(curr._payload); - if (target_file.exists()) { - astring new_file_name = find_unique_backup_name(curr._payload); - if (!new_file_name) { - BASE_LOG(astring("failed to calculate new filename for ") + curr._payload); - keyword_good = false; // cancel the overwrite, couldn't backup. - } else { - // make a backup of the file by moving the old file name to the - // new file name, which should be unique, and then the current - // name is all clear to be written as a new file. -// BASE_LOG(astring("backing up ") + curr._payload + " --> " + new_file_name); - int retval = rename(curr._payload.s(), new_file_name.s()); - if (retval) { - BASE_LOG(astring("failed to rename ") + curr._payload + " as " + new_file_name); - keyword_good = false; // cancel the overwrite, couldn't backup. - } - } - } - } - - byte_filer *targo = NIL; - if (keyword_good) targo = new byte_filer(curr._payload, "wb"); - byte_array uncompressed(256 * KILOBYTE); // fluff it out to begin with. - byte_array temp(256 * KILOBYTE); - - bool first_read = true; - // true if there haven't been any reads of the file before now. - bool too_tiny_complaint_already = false; - // becomes true if we complain about the file's size being larger than - // expected. this allows us to only complain once about each file. - - // read a chunk at a time out of our exe image and store it into the - // target file. - while (first_read || !our_exe.eof()) { - first_read = false; - un_int real_size = 0, packed_size = 0; - // read in the real size from the file. - bool worked = manifest_chunk::read_an_obscured_int(our_exe, real_size); - if (!worked) { - show_message(a_sprintf("failed while reading real size " - "for item #%d: ", festdex) + curr._payload, ERROR_TITLE); - return 99; - } - // read in the packed size now. - worked = manifest_chunk::read_an_obscured_int(our_exe, packed_size); - if (!worked) { - show_message(a_sprintf("failed while reading packed size " - "for item #%d: ", festdex) + curr._payload, ERROR_TITLE); - return 99; - } - - // make sure we don't eat the whole package--did the file end? - if ( (real_size == -1) && (packed_size == -1) ) { - // we've hit our sentinel; we've already unpacked all of this file. - break; - } - -#ifdef DEBUG_STUB - BASE_LOG(a_sprintf("chunk packed_size=%d, real_size=%d", packed_size, - real_size)); -#endif - - // now we know how big our next chunk is, so we can try reading it. - if (packed_size) { - int ret = our_exe.read(temp, packed_size); - if (ret <= 0) { - show_message(a_sprintf("failed while reading item #%d: ", festdex) - + curr._payload, ERROR_TITLE); - return 99; - } else if (ret != packed_size) { - show_message(a_sprintf("bad trouble ahead, item #%d had different " - " size on read (expected %d, got %d): ", festdex, packed_size, - ret) + curr._payload, ERROR_TITLE); - } - - uncompressed.reset(real_size + KILOBYTE); // add some for paranoia. - uLongf destlen = uncompressed.length(); - int uncomp_ret = uncompress(uncompressed.access(), &destlen, - temp.observe(), packed_size); - if (uncomp_ret != Z_OK) { - show_message(a_sprintf("failed while uncompressing item #%d: ", - festdex) + curr._payload, ERROR_TITLE); - return 99; - } - - if (int(destlen) != real_size) { - LOG(a_sprintf("got a different unpacked size for item #%d: ", - festdex) + curr._payload); - } - - // update the remaining size for this data chunk. - size_left -= real_size; - if (size_left < 0) { - if (!too_tiny_complaint_already) { - LOG(a_sprintf("item #%d was larger than expected (non-fatal): ", - festdex) + curr._payload); - too_tiny_complaint_already = true; - } - } - // toss the extra bytes out. - uncompressed.zap(real_size, uncompressed.length() - 1); - - if (targo) { - // stuff the data we read into the target file. - ret = targo->write(uncompressed); - if (ret != uncompressed.length()) { - show_message(a_sprintf("failed while writing item #%d: ", festdex) - + curr._payload, ERROR_TITLE); - return 93; - } - } - } - } - if (targo) targo->close(); - WHACK(targo); - // the file's written, but now we slap it's old time on it too. - file_time t; - if (!t.unpack(curr.c_filetime)) { - show_message(astring("failed to interpret timestamp for ") - + curr._payload, ERROR_TITLE); - return 97; - } - // put the timestamp on the file. - t.set_time(curr._payload); -// utimbuf held_time; -// held_time.actime = t.raw(); -// held_time.modtime = t.raw(); -// // put the timestamp on the file. -// utime(curr._payload.s(), &held_time); - } - - // now that we're pretty sure the file exists, we can run it if needed. - if ( (curr._flags & TARGET_EXECUTE) && keyword_good) { - // change the mode on the target file so we can execute it. - chmod(curr._payload.s(), 0766); - astring prev_dir = application_configuration::current_directory(); - - BASE_LOG(astring("launching ") + curr._payload); - if (!!curr._parms) - BASE_LOG(astring(" with parameters: ") + curr._parms); - BASE_LOG(astring('-', 76)); - - basis::un_int kid; - basis::un_int retval = launch_process::run(curr._payload, curr._parms, - launch_process::AWAIT_APP_EXIT | launch_process::HIDE_APP_WINDOW, kid); - if (retval != 0) { - if (! (curr._flags & IGNORE_ERRORS) ) { - if (curr._flags & QUIET_FAILURE) { - // no message box for this, but still log it. - LOG(astring("failed to launch process, targ=") - + curr._payload + " with parms " + curr._parms - + a_sprintf(" error=%d", retval)); - } else { - show_message(astring("failed to launch process, targ=") - + curr._payload + " with parms " + curr._parms - + a_sprintf(" error=%d", retval), ERROR_TITLE); - } - return retval; // pass along same exit value we were told. - } else { - LOG(astring("ignoring failure to launch process, targ=") - + curr._payload + " with parms " + curr._parms - + a_sprintf(" error=%d", retval)); - } - } - - chdir(prev_dir.s()); // reset directory pointer, just in case. - - BASE_LOG(astring('-', 76)); - } - - } - -#ifdef __WIN32__ - whack_simplistic_window(f_window); -#endif - - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -HOOPLE_MAIN(unpacker_stub, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/applications/bundler/version.ini b/core/applications/bundler/version.ini deleted file mode 100644 index 18196156..00000000 --- a/core/applications/bundler/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description = Application Bundler -root = bundler -name = Bundler -extension = exe - diff --git a/core/applications/example_application/example_application.cpp b/core/applications/example_application/example_application.cpp deleted file mode 100644 index c6a27913..00000000 --- a/core/applications/example_application/example_application.cpp +++ /dev/null @@ -1,137 +0,0 @@ -////////////// -// Name : Simple Application Example -// Author : Chris Koeritz -////////////// -// Copyright (c) 2006-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -//! An example of a bare-bones hoople application. -/*! - This application provides a very simple example for how we create programs - based on the HOOPLE code. -*/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -//using namespace processes; -using namespace structures; -using namespace unit_test; - -const int CHECKING_INTERVAL = 4 * SECOND_ms; - // this many milliseconds elapses between checks on shutdown conditions. - -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - // define a macro that will send diagnostic output to the app's logger. - -////////////// - -class application_example : virtual public unit_base, virtual public application_shell -{ -public: - application_example(); - ~application_example(); - - DEFINE_CLASS_NAME("application_example"); - - bool already_running(); - //!< true if this program is already running. - - virtual void handle_timer(); - //!< called by timer events from anchor window. - - virtual void handle_startup(); - //!< begins our service's activity. - - virtual void handle_shutdown(); - //!< catches the graceful shutdown so our objects get closed normally. - - virtual int execute(); - //!< the root of the program's activity. - - int print_instructions(); - //!< describes the available command line options for this program. - -private: - singleton_application _app_lock; //!< our inter-application synchronizer. -}; - -////////////// - -application_example::application_example() -: application_shell(), - _app_lock(static_class_name()) -{} - -application_example::~application_example() -{} - -int application_example::print_instructions() -{ - FUNCDEF("print_instructions"); - LOG("no instructions at this time."); - return 1; -} - -void application_example::handle_startup() -{ - FUNCDEF("handle_startup"); - LOG("starting up now."); -} - -void application_example::handle_shutdown() -{ - FUNCDEF("handle_shutdown"); - LOG("shutting down now."); -} - -void application_example::handle_timer() -{ - FUNCDEF("handle_timer"); - LOG("timer blip."); -} - -bool application_example::already_running() -{ return _app_lock.already_running(); } - -int application_example::execute() -{ - FUNCDEF("execute"); - command_line cmds(_global_argc, _global_argv); - -//hmmm: test for command line options that are supported. - - // make sure this app is not running already. - if (already_running()) { - return 0; - } - -//need anchor window online for this. -// anchor_window::launch(*this, GET_INSTANCE_HANDLE(), class_name(), CHECKING_INTERVAL); - - ASSERT_EQUAL(astring(class_name()), astring("application_example"), - "simple application name check"); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(application_example, ) - diff --git a/core/applications/example_application/makefile b/core/applications/example_application/makefile deleted file mode 100644 index 04094dbc..00000000 --- a/core/applications/example_application/makefile +++ /dev/null @@ -1,13 +0,0 @@ -CONSOLE_MODE = t - -include cpp/variables.def - -PROJECT = example_application -TYPE = application -SOURCE = -TARGETS = example_application.exe -LOCAL_LIBS_USED = application unit_test loggers configuration processes filesystem textual \ - structures timely basis - -include cpp/rules.def - diff --git a/core/applications/load_test_tools/makefile b/core/applications/load_test_tools/makefile deleted file mode 100644 index 152b9c93..00000000 --- a/core/applications/load_test_tools/makefile +++ /dev/null @@ -1,13 +0,0 @@ -CONSOLE_MODE = t - -include cpp/variables.def - -PROJECT = load_test_tools -TYPE = application -SOURCE = -TARGETS = memory_hog.exe -LOCAL_LIBS_USED = application unit_test loggers configuration processes filesystem textual \ - structures timely basis - -include cpp/rules.def - diff --git a/core/applications/load_test_tools/memory_hog.cpp b/core/applications/load_test_tools/memory_hog.cpp deleted file mode 100644 index ab24468b..00000000 --- a/core/applications/load_test_tools/memory_hog.cpp +++ /dev/null @@ -1,176 +0,0 @@ -////////////// -// Name : memory_hog -// Author : Chris Koeritz -////////////// -// Copyright (c) 2012-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -//! This application lives to grub up lots of memory. -/*! - You can specify how much memory the application should consume. - Occasionally it will riffle through its hamstered away memory to try to - ensure that it stays in real memory rather than virtual memory. -*/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -//using namespace processes; -using namespace structures; -using namespace unit_test; - -const int MEMORY_CHECKING_INTERVAL = 0.5 * SECOND_ms; - // this many milliseconds elapses between checks on shutdown conditions. - -#define BASE_LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - // define a macro that will send diagnostic output to the app's logger. - -////////////// - -class application_example : virtual public unit_base, virtual public application_shell -{ -public: - application_example(); - ~application_example(); - - DEFINE_CLASS_NAME("application_example"); - - virtual void handle_timer(); - //!< called by timer events from anchor window. - - virtual void handle_startup(); - //!< begins our service's activity. - - virtual void handle_shutdown(); - //!< catches the graceful shutdown so our objects get closed normally. - - virtual int execute(); - //!< the root of the program's activity. - - int print_instructions(); - //!< describes the available command line options for the ULS. - -private: - singleton_application _app_lock; //!< our inter-application synchronizer. -}; - -////////////// - -application_example::application_example() -: application_shell(), - _app_lock(static_class_name()) -{} - -application_example::~application_example() -{} - -int application_example::print_instructions() -{ - FUNCDEF("print_instructions"); - BASE_LOG("\ -\nUsage: memory_hog {memorySize}\n\ -\n\ - This application will consume a requested amount of memory until either\n\ -(1) the applications is interrupted or terminated, or (2) the application\n\ -is no longer given memory when it requests it (whether due to account limits\n\ -or to a lack of remaining allocatable memory), or (3) the requested amount\n\ -has been allocated.\n\ - The application will then sit back idly and occasionally riffle through its\n\ -big bag of memory in an attempt to keep most of it in physical memory rather\n\ -than virtual memory.\n\ - This is not a hostile act unless used unwisely; this program is intended\n\ -to get an unloaded machine into a predictable memory state.\n\ - **DO NOT use this program without system administrator permission.**" -); - return 1; -} - -void application_example::handle_startup() -{ - FUNCDEF("handle_startup"); - LOG("starting up now."); -} - -void application_example::handle_shutdown() -{ - FUNCDEF("handle_shutdown"); - LOG("shutting down now."); -} - -void application_example::handle_timer() -{ - FUNCDEF("handle_timer"); - LOG("timer blip."); -} - -int application_example::execute() -{ - FUNCDEF("execute"); - command_line cmds(_global_argc, _global_argv); - -LOG(a_sprintf("cmd line has %d entries", cmds.entries())); - - if (cmds.entries() < 1) { - BASE_LOG("You need to provide a number on the command line...."); - return print_instructions(); - } - - astring size_as_text = cmds.get(0).text(); - -LOG(a_sprintf("hoo hah! got a text thingy of: %s", size_as_text.s())); - - if (size_as_text.find('.') < 0) { - // we don't see this as floating point, but we will move on as if it's - // an integer and can become floating point. - size_as_text += ".0"; - } - - double how_fat = size_as_text.convert(double(0.0)); - -LOG(a_sprintf("got a number from user of: %f", how_fat)); - -//okay, now that we've got that handled, -// we want to have a hash table of byte arrays. -// int hash or whatever? we want it indexable and fast access on a unique id. -// that will be our consumption tactic. -// we will allocate some chunk size in the byte arrays. maybe max of a meg. -// then we just add as many byte arrays as we need to to get to the requested amount. -// don't just allocate them all at the same size; vary it a bunch, like say 20% of -// our maximum allotance. -// so randomly pull memory until we get what we want. -// if we get a memory failure, slack off until we can try to get more. -// if we cannot get memory within certain number of failures, report this and bail out. -// we do not want to crash the machine. -// once we've got our memory, start playing with it. -// every so often, -// pick a random number of items to play with, -// go to each item (which is a byte array), -// pick a random segment of the byte array to look at, -// read the contents of the memory there. that's it, nothing stressful. -// repeat the memory play until forever. -// enhancement, allow them to provide a run time. time out after that elapses. - - return 0; -} - -////////////// - -HOOPLE_MAIN(application_example, ) - diff --git a/core/applications/makefile b/core/applications/makefile deleted file mode 100644 index 582277c0..00000000 --- a/core/applications/makefile +++ /dev/null @@ -1,7 +0,0 @@ -include variables.def - -PROJECT = core_applications -BUILD_BEFORE = bookmark_tools bundler example_application nechung utilities - -include rules.def - diff --git a/core/applications/nechung/cgi_nechung.cpp b/core/applications/nechung/cgi_nechung.cpp deleted file mode 100644 index a7478245..00000000 --- a/core/applications/nechung/cgi_nechung.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/*****************************************************************************\ -* * -* Name : CGI nechung * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1997-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! @file cgi_nechung.cpp Spits out a CGI appropriate chunk of text with a fortune in it. - -#include "nechung_oracle.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -//using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; - -#undef LOG -#define LOG(s) program_wide_logger::get().log(s, 0) - -////HOOPLE_STARTUP_CODE; -//hmmm: need this back when things are ready! - -#define DEFAULT_FORTUNE_FILE "fortunes.dat" - -int main(int argc, char *argv[]) -{ - SETUP_CONSOLE_LOGGER; - - astring name; - astring index; - if (argc > 1) { - // use the first command line argument. - name = argv[1]; - } else { - // if nothing on the command line, then use our defaults. - name = environment::get("NECHUNG"); - // first try the environment variable. - if (!name) name = DEFAULT_FORTUNE_FILE; - // next, use the hardwired default. - } - - if (name.length() < 5) { - LOG(astring("nechung:: file name is too short (") + name + ")."); - return 1; - } - filename index_file_name(name); - astring extension(index_file_name.extension()); - int end = index_file_name.raw().end(); -#ifdef DEBUG_NECHUNG - LOG(astring("fortune filename is ") + name); - LOG(astring("extension is ") + extension); -#endif - astring tmp = index_file_name; - tmp.zap( (end + 1) - extension.length(), end); - tmp += "idx"; - astring base_part = filename(tmp).basename(); - index_file_name = environment::get("TMP") + "/" + base_part; -#ifdef DEBUG_NECHUNG - LOG(astring("index file is ") + index_file_name); -#endif - index = index_file_name; - - nechung_oracle some_fortunes(name, index); - // send the header for html text. - printf("content-type: text/html\n\n"); - // send the preliminary gunk. - printf("\n"); -//old text color #33ccff - printf("\n"); - printf("\n"); - - astring to_show = some_fortunes.pick_random(); - int line_posn = 0; - for (int i = 0; i < to_show.length(); i++) { - if (to_show[i] == ' ') { - // spaces get translated to one non-breaking space. - printf(" "); - line_posn++; - } else if (to_show[i] == '\t') { - // tabs get translated to positioning at tab stops based on eight. - int to_add = 8 - line_posn % 8; - for (int j = 0; j < to_add; j++) printf(" "); - line_posn += to_add; - } else if (to_show[i] == '\r') - continue; - else if (to_show[i] == '\n') { - printf("
    %c", to_show[i]); - line_posn = 0; - } else { - printf("%c", to_show[i]); - line_posn++; - } - } - printf("\n"); - printf("
    \n"); - printf("
    \n"); - printf("\n"); - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/applications/nechung/example.txt b/core/applications/nechung/example.txt deleted file mode 100644 index 4a08ef93..00000000 --- a/core/applications/nechung/example.txt +++ /dev/null @@ -1,76 +0,0 @@ -/*****************************************************************************\ -* * -* Name : nechung database format sample * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Describes the database format. The "fields" can be any kind of text, * -* including special characters. The one reserved term is a line containing * -* a single tilde character ('~') followed by the appropriate line ending. * -* That marks the beginning of the next record. Nechung will automatically * -* index the file based on these separators whenever the database changes. * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ -~ -Om Muni Muni Maha Muni Yea Swaha -~ -Gun dro nga tang yul nge nga -Gewa ju jik dza nyon druk -Nye nyon nyi shu shen gyur shi -Sem chung nga jik di dak o. -~ -DEVIL -EVIL -GOOD -GOD -~ -Nam Myoho Renge Kyo -~ -Om Mani Padme Hum -~ -Om Shanthi Om -~ -Om A Ra Ba Tsa Na Di -~ -The man in whom Tao -Acts without impediment -Harms no other being -By his actions -Yet he does not know himself -To be "kind," to be "gentle" -~ -When an archer is shooting for nothing -He has all his skill. -If he shoots for a brass buckle -He is already nervous. -If he shoots for a prize of gold -He goes blind -Or sees two targets-- -He is out of his mind! - -His skill has not changed. But the prize -Divides him. He cares. -He thinks more of winning -Than of shooting-- -And the need to win -Drains him of power. -~ -By giving, resources; by ethics, bliss -~ -what you don't see is what you get when you don't look -~ -this bell's knelling is never quelled, - while service is rendered, -tin staccato splattered over cupric strands, - spraying crazed meaning to distant lands. -what is it? -~ -To conquer oneself is a greater task than conquering others diff --git a/core/applications/nechung/makefile b/core/applications/nechung/makefile deleted file mode 100644 index 484bf88e..00000000 --- a/core/applications/nechung/makefile +++ /dev/null @@ -1,13 +0,0 @@ -CONSOLE_MODE = t - -include cpp/variables.def - -PROJECT = nechung -TYPE = application -SOURCE = nechung_oracle.cpp nechung_version.rc -DEFINITIONS += __BUILD_STATIC_APPLICATION__ -UNDEFINITIONS += ENABLE_MEMORY_HOOK ENABLE_CALLSTACK_TRACKING -TARGETS = nechung.exe cgi_nechung.exe - -include cpp/rules.def - diff --git a/core/applications/nechung/nechung.cpp b/core/applications/nechung/nechung.cpp deleted file mode 100644 index 5c93f7c6..00000000 --- a/core/applications/nechung/nechung.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/*****************************************************************************\ -* * -* Name : nechung console application * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*****************************************************************************/ - -//! @file nechung.cpp The application base for the Nechung Oracle Program (NOP). - -#include "nechung_oracle.h" - -#include -#include -#include -#include -#include -#include - -//using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; - -///HOOPLE_STARTUP_CODE; -//hmmm: missing. - -#undef LOG -#define LOG(s) program_wide_logger::get().log((s), 0) -//hmmm: need ALWAYS_PRINT back in there! - -#define DEFAULT_FORTUNE_FILE "fortunes.dat" - -int main(int argc, char *argv[]) -{ - SETUP_CONSOLE_LOGGER; - - astring name; - astring index; - if (argc > 1) { - // use the first command line argument. - name = argv[1]; - } else { - // if nothing on the command line, then use our defaults. - name = environment::get("NECHUNG"); - // first try the environment variable. - if (!name) name = DEFAULT_FORTUNE_FILE; - // next, use the hardwired default. - } - - if (name.length() < 5) { - LOG(astring("nechung:: file name is too short (") + name + ")."); - return 1; - } - filename index_file_name(name); - astring extension(index_file_name.extension()); - int end = index_file_name.raw().end(); -#ifdef DEBUG_NECHUNG - LOG(astring("fortune filename is ") + name); - LOG(astring("extension is ") + extension); -#endif - astring tmp = index_file_name; - tmp.zap( (end + 1) - extension.length(), end); - tmp += "idx"; - astring base_part = filename(tmp).basename(); - index_file_name = environment::get("TMP") + "/" + base_part; -#ifdef DEBUG_NECHUNG - LOG(astring("index file is ") + index_file_name); -#endif - index = index_file_name; - - nechung_oracle some_fortunes(name, index); - some_fortunes.display_random(); - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/applications/nechung/nechung_oracle.cpp b/core/applications/nechung/nechung_oracle.cpp deleted file mode 100644 index fe34e1a9..00000000 --- a/core/applications/nechung/nechung_oracle.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/*****************************************************************************\ -* * -* Name : nechung_oracle * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*****************************************************************************/ - -#include "nechung_oracle.h" - -#include -#include -#include -#include -#include - -#include -#include - -//using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; - -#undef LOG -#define LOG(s) program_wide_logger::get().log(s, 0) -///hmmm: fix filter value to be ALWAYS_PRINT! - -const int MAX_LINE_LENGTH = 2048; - -nechung_oracle::nechung_oracle(const astring &nechung_filename, - const astring &index_filename) -: c_randomizer(), - c_filename_held(nechung_filename), - c_index_held(index_filename), - c_number_of_fortunes(0) -{ parse_file(); } - -nechung_oracle::~nechung_oracle() {} - -void nechung_oracle::parse_file() -{ - FUNCDEF("parse_file"); - // below is code for comparing dates on the fortune file and the index file. - byte_filer fortune_file(c_filename_held.s(), "rb"); -#ifdef DEBUG_NECHUNG - LOG(astring("filename=") + c_filename_held + " idx file=" + c_index_held); -#endif - if (!fortune_file.good()) - non_continuable_error(class_name(), func, "Cannot open fortune file."); - - byte_array buffer(MAX_LINE_LENGTH + 1); - // used throughout parsing for line storage. - - byte_filer index_file(c_index_held.observe(), "r"); - if (index_file.good()) { -#ifdef DEBUG_NECHUNG - LOG("index file exists"); -#endif - file_time index_time((FILE *)index_file.file_handle()); - file_time fortune_time((FILE *)fortune_file.file_handle()); - if (index_time >= fortune_time) { - // need to read in the list of indices - index_file.getline(buffer, MAX_LINE_LENGTH); - sscanf((char *)buffer.access(), "%d", &c_number_of_fortunes); -#ifdef DEBUG_NECHUNG - LOG(astring(astring::SPRINTF, "%d entries in index", - c_number_of_fortunes)); -#endif - return; - } - } - index_file.close(); - - // below is code for creating the list. - enum fortune_states { - chowing_separators, // looking for the breaks between fortunes. - adding_fortunes, // saw the separator so get ready for a new fortune. - chowing_fortunes, // currently in a fortune accumulating lines. - done_parsing // finished parsing the fortune file. - }; - - c_number_of_fortunes = 0; - fortune_states state = chowing_separators; - - int posn; - int_array fortune_posns; // our list of fortunes. - while (state != done_parsing) { -#ifdef DEBUG_NECHUNG - LOG(astring(astring::SPRINTF, "#%d", c_number_of_fortunes)); -#endif - if (fortune_file.eof()) { - // exit from the loop now... - state = done_parsing; - continue; - } - switch (state) { - case chowing_separators: { -#ifdef DEBUG_NECHUNG - LOG("chowseps, "); -#endif - posn = int(fortune_file.tell()); - if (posn < 0) - non_continuable_error(class_name(), func, "Cannot get file position."); - fortune_file.getline(buffer, MAX_LINE_LENGTH); -#ifdef DEBUG_NECHUNG - LOG(astring("got a line: ") + buffer); -#endif - if (buffer[0] != NECHUNG_SEPARATION_CHARACTER) state = adding_fortunes; - else { - // special casing is for when we see a separator on the line - // by itself versus when it is the beginning of a line. if the - // beginning of a line, we currently take that to mean the rest - // of the line is the fortune. - if (strlen((char *)buffer.access()) == 2) posn += 2; - else posn++; - state = adding_fortunes; - } - break; - } - case adding_fortunes: { -#ifdef DEBUG_NECHUNG - LOG("add forts, "); -#endif - fortune_posns += posn; - c_number_of_fortunes++; - state = chowing_fortunes; - break; - } - case chowing_fortunes: { -#ifdef DEBUG_NECHUNG - LOG("chow forts, "); -#endif - posn = int(fortune_file.tell()); - if (posn < 0) - non_continuable_error(class_name(), func, "Cannot get file size."); - fortune_file.getline(buffer, MAX_LINE_LENGTH); -#ifdef DEBUG_NECHUNG - LOG(astring(astring::SPRINTF, "got a line: %s", buffer.access())); - LOG(astring(astring::SPRINTF, "len is %d", strlen((char *)buffer.access()))); -#endif - if ( (buffer[0] == NECHUNG_SEPARATION_CHARACTER) - && (strlen((char *)buffer.access()) == 2) ) - state = chowing_separators; - else if (buffer[0] == NECHUNG_SEPARATION_CHARACTER) { - posn++; - state = adding_fortunes; - } - break; - } - case done_parsing: { - non_continuable_error(class_name(), func, "Illegal state reached."); - } - } - } - fortune_file.close(); - - // make a new index file. - index_file.open(c_index_held.observe(), "w"); - if (!index_file.good()) - non_continuable_error(class_name(), func, astring("Cannot open index file: ") + c_index_held); - astring to_write(astring::SPRINTF, "%d\n", c_number_of_fortunes); - index_file.write((abyte *)to_write.s(), to_write.length()); - for (int j = 0; j < c_number_of_fortunes; j++) { - to_write.sprintf("%d\n", fortune_posns[j]); - index_file.write((abyte *)to_write.s(), to_write.length()); - } - index_file.close(); -} - -astring nechung_oracle::pick_random() -{ - FUNCDEF("pick_random"); -#ifdef DEBUG_NECHUNG - LOG(astring("got to ") + func); -#endif - - byte_filer fortune_file(c_filename_held.s(), "rb"); - -///printf("num forts = %d\n", c_number_of_fortunes ); - - if (!fortune_file.good()) - non_continuable_error(class_name(), func, "Cannot open data file."); - int to_display = c_randomizer.inclusive(0, c_number_of_fortunes - 1); - -///printf("rand chose= %d\n", to_display); - -///// -///hmmm: this bit could be more efficient by just jumping to the Nth line -/// instead of reading through up to the Nth line. -///// - byte_filer index_file(c_index_held.observe(), "r"); - int chosen_posn = 0; // which position to read the chosen line at. - if (index_file.good()) { - astring accumulated_text; - byte_array buffer(MAX_LINE_LENGTH + 1); - for (int i = 0; i <= to_display; i++) { -#ifdef DEBUG_NECHUNG - accumulated_text += astring(astring::SPRINTF, "#%d: ", i); -#endif - index_file.getline(buffer, MAX_LINE_LENGTH); - sscanf((char *)buffer.access(), "%d", &chosen_posn); -#ifdef DEBUG_NECHUNG - accumulated_text += astring(astring::SPRINTF, "%d, ", chosen_posn); - if ((i + 1) % 5 == 0) accumulated_text += "\n"; -#endif - } -#ifdef DEBUG_NECHUNG - LOG(accumulated_text); -#endif - - } else { - non_continuable_error(class_name(), func, \ - astring("Could not open the index file \"") + c_index_held + "\""); - } - index_file.close(); -#ifdef DEBUG_NECHUNG - LOG(astring(astring::SPRINTF, "about to seek @ num %d and " - "index %d", to_display, chosen_posn)); -#endif - if (!fortune_file.seek(chosen_posn, byte_filer::FROM_START)) - non_continuable_error(class_name(), func, "Cannot seek to indexed position."); -#ifdef DEBUG_NECHUNG - LOG("after seek"); -#endif - - astring to_return; - byte_array temp(MAX_LINE_LENGTH + 1); - while (!fortune_file.eof()) { - int chars_read = fortune_file.getline(temp, MAX_LINE_LENGTH); - if (!chars_read) { - if (!fortune_file.eof()) { - non_continuable_error(class_name(), func, "Error while reading fortune."); - } else break; - } - if (temp[0] == NECHUNG_SEPARATION_CHARACTER) break; - else to_return += astring((char *)temp.access()); - } - return to_return; -} - -//hmmm: stolen from parser bits. reconnect when available. -bool is_eol(char to_check) -{ return (to_check == '\n') || (to_check == '\r'); } - -void nechung_oracle::display_random() -{ - astring to_show = pick_random(); - while (is_eol(to_show[to_show.end()])) - to_show.zap(to_show.end(), to_show.end()); - LOG(to_show); -} - diff --git a/core/applications/nechung/nechung_oracle.h b/core/applications/nechung/nechung_oracle.h deleted file mode 100644 index 31381b22..00000000 --- a/core/applications/nechung/nechung_oracle.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef NECHUNG_CLASS -#define NECHUNG_CLASS - -/*****************************************************************************\ -* * -* Name : nechung_oracle * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* This is the root nechung functionality. It provides a means of * -* randomly selecting an item out of a specially formatted file. If no index * -* file has previously been built for the file, then one is created. The * -* index file makes choosing a fortune randomly very quick; only a seek on * -* the much smaller index is needed in order to find the position of the * -* fortune to be shown. * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// Nechung works with a particular form of data file and will extract a random -// and hopefully auspicious message out of that file. An example of the file -// format is: -// msg1 -// ~ -// msg2 -// ~ -// (... more messages and tildes...) -// The tilde is the separation character mentioned below. - -#include -#include - -//#define DEBUG_NECHUNG - // uncomment this to get the debugging version. it is used by several files - // that are part of nechung. - -const char NECHUNG_SEPARATION_CHARACTER = '~'; - // this character separates the entries in the fortunes database. - -class nechung_oracle -{ -public: - nechung_oracle(const basis::astring &data_filename, const basis::astring &index_filename); - // the constructor needs the name of a nechung format data file in - // "data_filename" and the name of the index file to be used for that data - // file in "index_filename". - - virtual ~nechung_oracle(); - - DEFINE_CLASS_NAME("nechung_oracle"); - - basis::astring pick_random(); - // returns a randomly chosen fortune. - - void display_random(); - // selects an oracular pronouncement from the file and then shows it on - // standard output. - -private: - mathematics::chaos c_randomizer; // the random number generator we use. - basis::astring c_filename_held; // the data file's name. - basis::astring c_index_held; // the index file's name. - int c_number_of_fortunes; // how many fortunes exist in the file. - - void parse_file(); - // given the data file and index file, this will ensure that the index - // file is made up to date. it creates, if necessary, the file that - // contains the positions of fortunes in the data file. this is what - // we'll use to find the start of each fortune. if the file already - // exists, then it will just retrieve the number of fortunes from the index - // file. after this method, the pick_random() and display_random() methods - // are available. - - // disallowed. - nechung_oracle(const nechung_oracle &); - nechung_oracle &operator =(const nechung_oracle &); -}; - -#endif - diff --git a/core/applications/nechung/readme b/core/applications/nechung/readme deleted file mode 100644 index ed2e1822..00000000 --- a/core/applications/nechung/readme +++ /dev/null @@ -1,7 +0,0 @@ - -The Nechung oracle is a person manifesting the spirit of Dorje Drakden who is -the protector divinity of the Dalai Lama. - -This program cannot attempt to fulfil the same role as the Nechung oracle, but -hopefully it will give you good advice relating to your needs and your life. - diff --git a/core/applications/nechung/test_nechung.sh b/core/applications/nechung/test_nechung.sh deleted file mode 100644 index 3acabd95..00000000 --- a/core/applications/nechung/test_nechung.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# prints out the time it takes to run the nechung oracle a thousand times. -# the number of seconds in the result should be equivalent to the number of -# milliseconds that nechung takes to run and produce a fortune, on average. - -export i=0 - -mdate -while [ $i -le 1000 ]; do -# echo $i - nechung >/dev/null - let i++ -done -mdate diff --git a/core/applications/nechung/version.ini b/core/applications/nechung/version.ini deleted file mode 100644 index 7b4eb044..00000000 --- a/core/applications/nechung/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description=Nechung Oracle Program -root=nechung -name=Nechung -extension=exe - diff --git a/core/applications/utilities/await_app_exit.cpp b/core/applications/utilities/await_app_exit.cpp deleted file mode 100644 index ec604ae4..00000000 --- a/core/applications/utilities/await_app_exit.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* -* Name : await_app_exit -* Author : Chris Koeritz -* Purpose: * -* This program waits for a particular application to exit before this app * -* itself exits. This allows a pause while another possibly slow process is * -* leaving. * -* Copyright (c) 2003-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace filesystem; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; - -#undef BASE_LOG -#define BASE_LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) -#undef LOG -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -class await_app_exit : public application_shell -{ -public: - await_app_exit() : application_shell() {} - DEFINE_CLASS_NAME("await_app_exit"); - int execute(); -}; - -int await_app_exit::execute() -{ - FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - if (_global_argc < 3) { - BASE_LOG("This program needs two parameters on the command line. The first is an"); - BASE_LOG("application name (e.g. 'blofeld.exe' is a valid example--no path should be"); - BASE_LOG("included but the .exe suffix must be included) to seek out in the process"); - BASE_LOG("list and the second parameter is the time to wait for it to exit (in seconds)."); - BASE_LOG("This program will not exit until the specified application is no longer"); - BASE_LOG("running or the timeout elapses. If the timeout elapses, then a failure exit"); - BASE_LOG("will occur from this program so that it is known that the target application"); - BASE_LOG("never exited."); - return 2; - } - - astring app_name = _global_argv[1]; // get the app's name. - astring duration = _global_argv[2]; // get the time to wait. - int timeout = duration.convert(0) * 1000; - if (timeout < 0) { - LOG(astring("The timeout specified is invalid: ") + duration); - return 3; - } - - // now see if that app is even running. - process_control querier; - process_entry_array processes; - querier.query_processes(processes); - int_set pids; - time_stamp when_to_leave(timeout); // when we should stop checking. - - // wait for the app to go away. - while (querier.find_process_in_list(processes, app_name, pids)) { - // the program of interest is still running. - time_control::sleep_ms(100); - querier.query_processes(processes); - if (time_stamp() > when_to_leave) { - LOG(astring("The timeout elapsed and ") + app_name + " is still running."); - return 4; - } - } - LOG(astring("The ") + app_name + " process has exited."); - return 0; -} - -HOOPLE_MAIN(await_app_exit, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/applications/utilities/bytedump.cpp b/core/applications/utilities/bytedump.cpp deleted file mode 100644 index a30d16d1..00000000 --- a/core/applications/utilities/bytedump.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/*****************************************************************************\ -* * -* Name : dump_bytes * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Prints the files specified out in terms of their hex bytes. * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace filesystem; -using namespace structures; -using namespace textual; - -///HOOPLE_STARTUP_CODE; - -const int MAXIMUM_BYTEDUMP_BUFFER_SIZE = 32768; - // this is the size of the chunk we read from the files at a time. it is - // important to make this a multiple of 16, since that's the size of the line - // we use in the byte dumping. - -#define console program_wide_logger::get() - -int print_instructions_and_exit(char *program_name) -{ - console.log(astring(astring::SPRINTF, "\n\ -Usage:\n\t%s filename [filename]\n\n\ -Prints out (on standard output) a abyte dump of the files specified.\n\n", - program_name), ALWAYS_PRINT); - return 12; -} - -int main(int argc, char *argv[]) -{ - SETUP_CONSOLE_LOGGER; - - if (argc <= 1) return print_instructions_and_exit(argv[0]); - else { - int current_parameter = 0; -/* - if (argv[1][0] == '-') { - if (argv[1][1] == 't') { - current_parameter++; - open_file_as_text = true; - } else if (argv[1][1] == 'b') { - current_parameter++; - open_file_as_text = false; - } else print_instructions_and_exit(argv[0]); - } -*/ - bool past_first_file = false; - while (++current_parameter < argc) { - if (past_first_file) { - // we're into the second file so start using some white space. - console.log(astring::empty_string(), ALWAYS_PRINT); - console.log(astring::empty_string(), ALWAYS_PRINT); - } - past_first_file = true; // set condition for next time. - astring name = argv[current_parameter]; - byte_filer current(name, "rb"); - if (!current.good()) { - console.log(astring("Cannot find the file named \"") + name + astring("\"."), - ALWAYS_PRINT); - continue; - } - abyte buff[MAXIMUM_BYTEDUMP_BUFFER_SIZE + 10]; - // buffer plus some extra room. - bool printed_header = false; - int current_label = 0; - while (true) { - int bytes_read = current.read(buff, MAXIMUM_BYTEDUMP_BUFFER_SIZE); - if (bytes_read <= 0) break; // no contents. -//console.log(astring(astring::SPRINTF, "read %d bytes", bytes_read)); - if (!printed_header) { - console.log(name + ":", ALWAYS_PRINT); - console.log(astring::empty_string(), ALWAYS_PRINT); // blank line. - printed_header = true; - } - astring to_log = byte_formatter::text_dump(buff, bytes_read, - current_label); - if (to_log[to_log.end()] == '\n') - to_log.zap(to_log.end(), to_log.end()); - console.log(to_log, ALWAYS_PRINT); - current_label += bytes_read; - } - } - } - return 0; -} - diff --git a/core/applications/utilities/checker.cpp b/core/applications/utilities/checker.cpp deleted file mode 100644 index c56cec3f..00000000 --- a/core/applications/utilities/checker.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/*****************************************************************************\ -* * -* Name : checker * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Generates checksums for a set of files. * -* * -******************************************************************************* -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace basis; -using namespace structures; -using namespace timely; - -const int buffer_size = 4096; - -//HOOPLE_STARTUP_CODE; - -//#define DEBUG_CHECKER - // uncomment for noisy version. - -void print_instructions_and_exit(char *program_name) -{ - printf("\n\ -Usage:\n\t%s [-t] filename [filename]\n\n\ -This program generates a checksum for each file that is entered on the\n\ -command line. The checksum is (hopefully) an architecture independent\n\ -number that is a very compressed representation of the file gestalt.\n\ -If one compares two copies of a file, then the checksums should be identical.\n\ -This is a useful test of whether a file copy or a program download is\n\ -successful in making an identical version of the file. In particular, if the\n\ -file is made slightly bigger or smaller, or if an item in the file is changed,\n\ -then the checksums of the two versions should be different numbers.\n\n\ -The -b flag is used if the files are to be compared as binary files, and this\n\ -is also the default. The -t flag is used if the files are to be compared as\n\ -text files.\n", - program_name); - exit(1); -} - -#define HIGHEST_CHECK 32714 - -// do_checksum: takes the specified file name and generates a checksum for it. -// if the file is inaccessible or, at any point, reading it returns an -// error message, then a negative value is returned. -int do_checksum(char *file_name, int open_as_a_text_file) -{ - char file_open_mode[10]; - if (open_as_a_text_file) strcpy(file_open_mode, "rt"); - else strcpy(file_open_mode, "rb"); - FILE *opened_file = fopen(file_name, file_open_mode); -#ifdef DEBUG_CHECKER - LOG(astring("opened ") + file_name); -#endif - if (!opened_file) return common::NOT_FOUND; - int characters_read = 0; - int current_checksum_value = 0; - char buffer_chunk[buffer_size]; - while (!feof(opened_file)) { - characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size, - opened_file)); - // if result is 0 or negative, stop messing with the file. -#ifdef DEBUG_CHECKER - LOG(a_sprintf("char read = %d", characters_read)); -#endif - if (characters_read <= 0) { - if (characters_read < 0) current_checksum_value = -1; - else if (current_checksum_value == 0) current_checksum_value = -1; - break; - } - current_checksum_value = (current_checksum_value - + checksums::bizarre_checksum((abyte *)buffer_chunk, characters_read)) - % HIGHEST_CHECK; -#ifdef DEBUG_CHECKER - LOG(a_sprintf("current checksum=%d", current_checksum_value)); -#endif - } - fclose(opened_file); - return int(current_checksum_value); -} - -// do_fletcher_checksum: takes the specified file name and generates a fletcher -// checksum for it. if the file is inaccessible or, at any point, -// reading it returns an error message, then a negative value is returned. -int do_fletcher_checksum(char *file_name, int open_as_a_text_file) -{ - char file_open_mode[10]; - if (open_as_a_text_file) strcpy(file_open_mode, "rt"); - else strcpy(file_open_mode, "rb"); - FILE *opened_file = fopen(file_name, file_open_mode); -#ifdef DEBUG_CHECKER - LOG(astring("opened ") + file_name); -#endif - if (!opened_file) return common::NOT_FOUND; - int characters_read = 0; - int current_checksum_value = 0; - char buffer_chunk[buffer_size]; - while (!feof(opened_file)) { - characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size, - opened_file)); - // if result is 0 or negative, stop messing with the file. -#ifdef DEBUG_CHECKER - LOG(a_sprintf("char read = %d", characters_read)); -#endif - if (characters_read <= 0) { - if (characters_read < 0) current_checksum_value = -1; - else if (current_checksum_value == 0) current_checksum_value = -1; - break; - } - current_checksum_value = checksums::rolling_fletcher_checksum - ((uint16)current_checksum_value, (abyte *)buffer_chunk, - characters_read); -#ifdef DEBUG_CHECKER - LOG(a_sprintf("current checksum=%d", current_checksum_value)); -#endif - } - fclose(opened_file); - return current_checksum_value; -} - -int main(int argc, char *argv[]) -{ - char name[200]; - - // if the file is to be read as a text file, then this is true. - int open_file_as_text = false; - - if (argc <= 1) print_instructions_and_exit(argv[0]); - else { - int current_parameter = 0; - if (argv[1][0] == '-') { - if (argv[1][1] == 't') { - current_parameter++; - open_file_as_text = true; - } else if (argv[1][1] == 'b') { - current_parameter++; - open_file_as_text = false; - } else print_instructions_and_exit(argv[0]); - } - bool printed_header = false; - while (++current_parameter < argc) { - if (!printed_header) { - printed_header = true; - printf("%s\n", (astring("[ checker running at ") + time_stamp::notarize(true) + "]").s()); - printf("bizarro fletcher filename\n"); - printf("======= ======== ========\n"); - } - strcpy(name, argv[current_parameter]); - int checksum_of_file = do_checksum(name, open_file_as_text); - int fletcher_chksum = do_fletcher_checksum(name, open_file_as_text); - if (checksum_of_file >= 0) { - printf("%s", a_sprintf(" %05d 0x%04x %s\n", checksum_of_file, - fletcher_chksum, name).s()); - } else { - printf("%s", a_sprintf("%s is inaccessible.\n", name).s()); - } - } - } - return 0; -} - diff --git a/core/applications/utilities/ini_edit.cpp b/core/applications/utilities/ini_edit.cpp deleted file mode 100644 index a56bf6a9..00000000 --- a/core/applications/utilities/ini_edit.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/*****************************************************************************\ -* * -* Name : ini_edit * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Provides command line ini editing capabilities. These include both * -* reading of ini files and writing new entries to them. * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; - -#undef LOG -#define LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) - -class ini_editor : public application_shell -{ -public: - ini_editor() - : application_shell(), - _app_name(filename(application::_global_argv[0]).basename()) {} - DEFINE_CLASS_NAME("ini_editor"); - virtual int execute(); - int print_instructions(); - -private: - astring _app_name; -}; - -int ini_editor::print_instructions() -{ - LOG(a_sprintf("\ -%s: This program needs five parameters to process an ini file.\n\ -There are two major operations, read and write. The type of operation\n\ -should be the first parameter. The other parameters are similar for both\n\ -operations, except for the last parameter. These are as follows:\n\ -Reading:\n\ -\tread inifile section entry defaultvalue\n\ - This reads the \"inifile\" specified and looks for the \"section\" and\n\ -\"entry\" name in the file. It will either return (via standard output)\n\ -the value found there or it will return the \"defaultvalue\". No error\n\ -will be raised if the entry is missing, but the default signals that no\n\ -value was defined.\n\ - Additionally, if the entry name is the special value \"whole_section\",\n\ -then the entire section will be read and returned as a CSV list. If the\n\ -section is empty, then the default string is returned instead.\n\ -Writing:\n\ -\twrite inifile section entry newvalue\n\ - This writes a new item with contents \"newvalue\" into the \"inifile\"\n\ -in the \"section\" at the \"entry\" specified. This should always succeed\n\ -unless the ini file is not writable (in which case an error should be\n\ -returned). Nothing is send to standard output for a write operation.\n\ -", _app_name.s())); - return 23; -} - -int ini_editor::execute() -{ - SETUP_CONSOLE_LOGGER; - - if (application::_global_argc < 6) return print_instructions(); - - astring operation = application::_global_argv[1]; - bool read_op = true; - if ( (operation[0] == 'w') || (operation[0] == 'W') ) read_op = false; - astring ini_file = application::_global_argv[2]; - astring section = application::_global_argv[3]; - astring entry = application::_global_argv[4]; - astring value = application::_global_argv[5]; - ini_configurator ini(ini_file, ini_configurator::RETURN_ONLY); - if (read_op) { - // read the entry from the ini file. - astring found; - if (entry.equal_to("whole_section")) { - // they want the whole section back at them. - string_table found; - bool worked = ini.get_section(section, found); - if (!worked) program_wide_logger::get().log(value, ALWAYS_PRINT); // default. - else { - // generate answer as csv. - astring temp; - list_parsing::create_csv_line(found, temp); - program_wide_logger::get().log(temp, ALWAYS_PRINT); // real value read. - } - } else { - bool worked = ini.get(section, entry, found); -/// program_wide_logger::get().eol(log_base::NO_ENDING); - if (!worked) program_wide_logger::get().log(value, ALWAYS_PRINT); // default. - else program_wide_logger::get().log(found, ALWAYS_PRINT); // real value read. - } - } else { - // write the entry to the ini file. - bool worked = ini.put(section, entry, value); - if (!worked) exit(28); // failure to write the entry. - } - - return 0; -} - -HOOPLE_MAIN(ini_editor, ) - diff --git a/core/applications/utilities/makefile b/core/applications/utilities/makefile deleted file mode 100644 index 2ba9d0eb..00000000 --- a/core/applications/utilities/makefile +++ /dev/null @@ -1,19 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -PROJECT = utilities -TYPE = application -ifeq "$(OMIT_VERSIONS)" "" - SOURCE += util_version.rc -endif -LOCAL_LIBS_USED = application loggers mathematics processes textual timely configuration filesystem structures basis -TARGETS = await_app_exit.exe bytedump.exe checker.exe ini_edit.exe mdate.exe splitter.exe -#check_address.exe check_eol.exe create_guid.exe cstrip.exe \ - empty.exe find_non_ascii.exe find_text.exe insert_before.exe \ - lexcaser.exe machine_name.exe merge_inis.exe net_calc.exe pi.exe \ - replace_text.exe show_args.exe show_versions.exe to_lower.exe uucat.exe -#VCPP_USE_SOCK=t - -include cpp/rules.def - diff --git a/core/applications/utilities/mdate.cpp b/core/applications/utilities/mdate.cpp deleted file mode 100644 index ffb1a662..00000000 --- a/core/applications/utilities/mdate.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/*****************************************************************************\ -* * -* Name : mdate * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Provides a more informative date command, providing milliseconds also. * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace structures; -using namespace timely; - -class mdate_app : public application_shell -{ -public: - virtual int execute() { - SETUP_CONSOLE_LOGGER; - program_wide_logger::get().log(time_stamp::notarize(false), ALWAYS_PRINT); - return 0; - } - DEFINE_CLASS_NAME("mdate_app"); -}; - -HOOPLE_MAIN(mdate_app, ) - diff --git a/core/applications/utilities/splitter.cpp b/core/applications/utilities/splitter.cpp deleted file mode 100644 index 0de8347a..00000000 --- a/core/applications/utilities/splitter.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/*****************************************************************************\ -* * -* Name : splitter * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Takes text as input and splits the lines so that they will fit on a * -* standard 80 column terminal. * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; - -const int MAX_BUFFER = 1024; - -class splitter_app : public application_shell -{ -public: - splitter_app() : application_shell() {} - - DEFINE_CLASS_NAME("splitter_app"); - - virtual int execute(); - - int print_instructions(); - -private: -}; - -////////////// - -int splitter_app::print_instructions() -{ - astring name = filename(_global_argv[0]).basename().raw(); - log(a_sprintf("%s usage:", name.s())); - log(astring::empty_string()); - log(a_sprintf("\ -This program splits long lines in input files into a more reasonable size.\n\ -Any filenames on the command line are split and sent to standard output.\n\ -The following options change how the splitting is performed:\n\ - --help or -?\tShow this help information.\n\ - --mincol N\tMinimum column to use for output.\n\ - --maxcol N\tMinimum column to use for output.\n\ -")); - return -3; -} - -int splitter_app::execute() -{ - command_line cmds(_global_argc, _global_argv); // parse the command line up. - - // retrieve any specific flags first. - astring temp; - int min_col = 0; - if (cmds.get_value("mincol", temp)) - min_col = temp.convert(min_col); - int max_col = 77; - if (cmds.get_value("maxcol", temp)) - max_col = temp.convert(max_col); - // look for help command. - int junk_index = 0; - if (cmds.find("help", junk_index, false) - || cmds.find('h', junk_index, false) - || cmds.find("?", junk_index, false) - || cmds.find('?', junk_index, false) ) { - print_instructions(); - return 0; - } - - // gather extra input files. - string_set input_files; - for (int i = 0; i < cmds.entries(); i++) { - const command_parameter &curr = cmds.get(i); - if (curr.type() == command_parameter::VALUE) { -//log(astring("adding input file:") + curr.text()); - input_files += curr.text(); - } - } - - astring accumulator; - for (int q = 0; q < input_files.length(); q++) { - byte_filer current(input_files[q], "r"); - if (!current.good()) continue; - while (!current.eof()) { - astring line_read; - int num_chars = current.getline(line_read, MAX_BUFFER); - if (!num_chars) continue; -//printf("line len=%d, cont=%s\n", line_read.length(), line_read.s()); - accumulator += line_read; -//// accumulator += '\n'; - } - } - - // now get from standard input if there weren't any files specified. - if (!input_files.length()) { - char input_line[MAX_BUFFER + 2]; - while (!feof(stdin)) { - char *got = fgets(input_line, MAX_BUFFER, stdin); - if (!got) break; -//printf("line=%s\n", got); - accumulator += got; -//// accumulator += '\n'; - } - } -//printf("splitting accum with %d chars...\n", accumulator.length()); - astring chewed; - string_manipulation::split_lines(accumulator, chewed, min_col, max_col); -//printf("chewed string now has %d chars...\n", chewed.length()); - printf("%s", chewed.s()); - return 0; -} - -////////////// - -HOOPLE_MAIN(splitter_app, ) - diff --git a/core/applications/utilities/splitter_test_1.txt b/core/applications/utilities/splitter_test_1.txt deleted file mode 100644 index f1a009ef..00000000 --- a/core/applications/utilities/splitter_test_1.txt +++ /dev/null @@ -1,33 +0,0 @@ - -FROOP Corporation - -Software Design Description (SDD) - -Document: Outcome and Diagnostic Standardization Proposal - -Project: Snoopter 5.1 - -Author: Chris Koeritz - -Created: 11/17/2004 - -Revision: 2 - -The Problem - - 1. The Snoopter product incorporates hundreds of C++ classes. Many of these define a set of outcomes that represent the exit status of a function invocation. This is a useful feature, rather than just returning a boolean result, but it has spread like wildfire and the set of outcomes today is very large. One part of the problem is that it is not clear to our customers what the outcomes even are, much less what they mean. - - 2. This impacts the logging of diagnostic information too. Sometimes the bare numerical values of outcomes are logged, whereas other times the outcome name is logged. While logging the names of outcomes is the preferred method, even then the set of names and what they mean is not necessarily clear. Another part of the problem is thus that diagnostic entries using outcomes are more difficult to decode than necessary. - - 3. Beyond the potential confusion that can arise from logging outcomes, some of our diagnostic entries are unclear or incomplete. The entries describe a problem, but sometimes they don't describe the object that the problem pertains to or the don't provide enough information to really understand what the problem is. Since each programmer writes their own log entries according to their own predilections, the diagnostic traces from Snoopter can be quirky and differ substantially from program to program. Some of this is unavoidable, but we need to provide better and more complete logging. - - 4. - -The Solution - - 1. One aspect of this project is to define a very low-level set of outcomes that cover most common function results. These will be used wherever possible as the outcomes returned by our C++ classes. There may still need to be a larger set of outcomes for certain classes, since their behavior may not make sense to describe at a very low-level. To support these extensions to the low-level set of outcomes, there needs to be a set of tools for adding new outcomes programmatically and in a way that is stable (i.e., the set does not change between releases unless intentionally modified) and verifiable (i.e., ensuring that no two outcomes share the same numeric value unless they are the same outcome). - - 2. It is important for the customer's sanity (and our own) that we have a way to produce a list of all outcomes in the program, their numerical values, and some description of what that outcome means. Part of the standardization project is to produce a tool capable of listing all of the Snoopter outcomes in just this way. - - 3. This project will create a set of guidelines for how to log the right amount of information when describing a situation in a log file. This will not be something that can be absolute and unvarying for everyone, but having a set of clear techniques for creating good log entries will be valuable. - diff --git a/core/applications/utilities/splitter_test_2.txt b/core/applications/utilities/splitter_test_2.txt deleted file mode 100644 index a20779ce..00000000 --- a/core/applications/utilities/splitter_test_2.txt +++ /dev/null @@ -1,8 +0,0 @@ - -this file used to be done wrong; the snow lion tags would be at the end of the line at the bottom -but they should have been auto-ejected to the next line. if it looks kind of right now, then the problem is gone. - - -Karma has four main characteristics. The first is its increasing effect: goodness heralds further goodness and evil heralds further evil. Secondly, karma is definite: in the long run, goodness always produces joy and negativity always produces suffering. Thirdly, one never experiences a joy or sorrow that does not have an according karmic cause. And lastly, the karmic seeds that are placed on the mind at the time of an action will never lose their potency even in a hundred million lifetimes, but will lie dormant within the mind until one day the conditions that activate them appear. - --- His Holiness the Dalai Lama from The Path to Enlightenment, published by Snow Lion Publications diff --git a/core/applications/utilities/version.ini b/core/applications/utilities/version.ini deleted file mode 100644 index f1a352ea..00000000 --- a/core/applications/utilities/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description = Small Utility Programs -root = util -name = Utility App -extension = exe - diff --git a/core/library/algorithms/makefile b/core/library/algorithms/makefile deleted file mode 100644 index 8cf4b298..00000000 --- a/core/library/algorithms/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = algorithms -TYPE = library -SOURCE = placeholder.cpp -TARGETS = algorithms.lib - -include cpp/rules.def - diff --git a/core/library/algorithms/placeholder.cpp b/core/library/algorithms/placeholder.cpp deleted file mode 100644 index 4878eca4..00000000 --- a/core/library/algorithms/placeholder.cpp +++ /dev/null @@ -1,4 +0,0 @@ - - -// sole purpose of this is to make the lib be generated, -// even though we currently do not have any code that lives inside it. diff --git a/core/library/algorithms/shell_sort.h b/core/library/algorithms/shell_sort.h deleted file mode 100644 index 7e9b2539..00000000 --- a/core/library/algorithms/shell_sort.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef SHELL_SORT_CLASS -#define SHELL_SORT_CLASS - -////////////// -// Name : shell_sort -// Author : Chris Koeritz -////////////// -// Copyright (c) 1991-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -namespace algorithms { - -//! Orders an array in O(n log n) time. -/*! - This algorithm is based on Kernighan and Ritchie's "The C Programming Language". -*/ - -template -void shell_sort(type v[], int n, bool reverse = false) - //!< this function sorts a C array of the "type" with "n" elements. - /*!< the "type" of object must support comparison operators. if "reverse" - is true, then the list is sorted highest to lowest. */ -{ - type temp; - int gap, i, j; - // the gap sizes decrease quadratically(?). they partition the array of - // items that need to be sorted into first two groups, then four, then - // eight, .... - // within each gap's worth of the array, the next loop takes effect... - for (gap = n / 2; gap > 0; gap /= 2) { - // the i indexed loop is the base for where the comparisons are made in - // the j indexed loop. it makes sure that each item past the edge of - // the gap sized partition gets considered. - for (i = gap; i < n; i++) { - // the j indexed loop looks at the values in our current gap and ensures - // that they are in sorted order. - if (!reverse) { - // normal ordering. - for (j = i - gap; j >= 0 && v[j] > v[j + gap]; j = j - gap) { - // swap the elements that are disordered. - temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; - } - } else { - // reversed ordering. - for (j = i - gap; j >= 0 && v[j] < v[j + gap]; j = j - gap) { - // swap the elements that are disordered. - temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; - } - } - } - } -} - -} // namespace. - -#endif // outer guard. - diff --git a/core/library/application/application_shell.cpp b/core/library/application/application_shell.cpp deleted file mode 100644 index a894bfac..00000000 --- a/core/library/application/application_shell.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/*****************************************************************************\ -* * -* Name : application_shell * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "application_shell.h" - -#include -#include -#include -#include -#include -#include - -#include -#ifdef __UNIX__ - #include - #include -#endif - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace mathematics; -using namespace textual; -using namespace timely; - -namespace application { - -const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; - // maximum command line that we'll deal with here. - -application_shell *not_so_hidden_pointer = NULL; // fodder for the single instance function. - -application_shell *application_shell::single_instance() { return not_so_hidden_pointer; } - -application_shell::application_shell() -: c_rando(), - c_exit_value(120) // if this is never changed from the default, that in itself is an error. -{ - // we create a combo logger for program-wide usage. - SETUP_COMBO_LOGGER; - not_so_hidden_pointer = this; // setup the single instance method. -} - -application_shell::~application_shell() -{ - if (not_so_hidden_pointer == this) not_so_hidden_pointer = NULL; // only scorch it when it's us. -} - -outcome application_shell::log(const base_string &to_print, int filter) -{ - outcome to_return = common::OKAY; - if (program_wide_logger::get().member(filter)) { - astring temp_log(to_print); - if (temp_log.length()) - temp_log.insert(0, time_stamp::notarize(true)); - to_return = program_wide_logger::get().log(temp_log, filter); - } - return to_return; -} - -int application_shell::execute_application() -{ - try { - c_exit_value = execute(); - } catch (const char *message) { - printf("caught exception:\n%s\n", message); - } catch (astring message) { - printf("caught exception:\n%s\n", message.s()); - } catch (...) { - printf("caught exception: unknown type!\n"); - } - return c_exit_value; -} - -} //namespace. - diff --git a/core/library/application/application_shell.h b/core/library/application/application_shell.h deleted file mode 100644 index 950eb919..00000000 --- a/core/library/application/application_shell.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef APPLICATION_SHELL_CLASS -#define APPLICATION_SHELL_CLASS - -/*****************************************************************************\ -* * -* Name : application_shell * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "base_application.h" - -#include -#include - -namespace application { - -//! The application_shell is a base object for console programs. -/*! - It is generally used in that context (console mode), but can be employed as the root of windowed - programs also. The application_shell provides a few features, such as logging functionality, - that make it a bit easier than starting up a program from scratch every time. -*/ - -class application_shell : public base_application -{ -public: - application_shell(); - //!< constructs an application_shell to serve as the root of the program. - - virtual ~application_shell(); - - DEFINE_CLASS_NAME("application_shell") - - static application_shell *single_instance(); - //!< in a program with a single application_shell extant, this gives out the instance. - /*!< if there are more than one application_shells floating around a program, then this - will only give out the most recently registered. note that this pointer is not the - slightest bit thread safe if it's changing after the application shell has been constructed, - and that it cannot be relied upon until that's happened either. be careful. do not use - it in any function that might be invoked during program shutdown. */ - - virtual int execute_application(); - //!< runs the base class's execute() method and catches any exceptions due to it. - /*!< you can override this method, but generally should never need to. the derived class's - method should catch exceptions and deal with them meaningfully also. */ - - int exit_value() const { return c_exit_value; } - //!< once the application has finished executing, this will contain the exit value. - - const mathematics::chaos &randomizer() const { return c_rando; } - //!< provides access to the random number generator owned by this app. - -// static basis::astring application_name(); - //!< returns the full name of the current application. - -// static basis::u_int process_id(); - //!< returns the process id for this task, if that's relevant on the OS. - -// static basis::astring current_directory(); - //!< returns the current directory as reported by the operating system. - - virtual basis::outcome log(const basis::base_string &to_print, int filter = basis::ALWAYS_PRINT); - //!< as above, logs a line "to_print" but only if the "filter" is enabled. - /*!< the second version uses the filter value to assess whether to print - the string or not. the string will not print if that filter is not - enabled for the program wide logger. */ - -#ifdef __UNIX__ -// static basis::astring get_cmdline_from_proc(); - //!< retrieves the command line from the /proc hierarchy on linux. -// static basis::astring query_for_process_info(); - //!< seeks out process info for a particular process. -#endif - -protected: - virtual int execute() = 0; - //!< forwards base_application responsibility upwards to derived objects. - /*!< this should not be invoked by anyone in general; it is automatically called from - the constructor of this class. */ - -private: - mathematics::chaos c_rando; //!< random number generator. - int c_exit_value; //!< how did things end up for the app? - - // not applicable. - application_shell(const application_shell &); - application_shell &operator =(const application_shell &); -}; - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/application/base_application.h b/core/library/application/base_application.h deleted file mode 100644 index 23a658a2..00000000 --- a/core/library/application/base_application.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef BASE_APPLICATION_CLASS -#define BASE_APPLICATION_CLASS - -////////////// -// Name : base_application -// Author : Chris Koeritz -////////////// -// Copyright (c) 2000-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include -#include - -namespace application { - -//! Provides a base object for the root application portion of a program. -/*! - This mainly defines an entry point into the application's real functionality. - Derived versions of the base_application can layer in more functionality as - appropriate for different types of applications. -*/ - -class base_application : public virtual basis::nameable -{ -public: - virtual const char *class_name() const = 0; // must be provided by implementor. - - virtual int execute() = 0; - //!< performs the main activity of this particular application object. - /*!< the method must be overridden by the derived object. a return value - for the program as a whole should be returned. */ -}; - -////////////// - -#if 0 - -//! This is an example usage of the base_application class... -class example_application : public base_application -{ -public: - example_application() : base_application() {} - DEFINE_CLASS_NAME("example_application"); - int execute() { /* do stuff and return final exit value. */ } -}; - -//! This is a sample main application for a console mode program... -int __example__main(int argc, char *argv[]) -{ - example_application root_program; - return root_program.execute(); -} - -#endif // example guard. - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/application/build_configuration.h b/core/library/application/build_configuration.h deleted file mode 100644 index 1ad87b6c..00000000 --- a/core/library/application/build_configuration.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef BUILD_CONFIGURATION_GROUP -#define BUILD_CONFIGURATION_GROUP - -////////////// -// Name : build configuration -// Author : Chris Koeritz -////////////// -// Copyright (c) 1995-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -/*! @file build_configuration.h - @brief Contains definitions that control how libraries and programs are built. - - These definitions can be used for declaring classes and functions that need - to be called from a DLL or that need to be compiled into a DLL. The import - style is used to declare that the item is being pulled out of a DLL. The - export style is used to declare that the item is being included in a DLL for - use by other functions. -*/ - -#include - -// Here is an example of a dynamic library header to be used by a particular library -// (presumably the gurpta library): -#if 0 - #ifndef GURPTA_DYNAMIC_LIBRARY_HEADER - #define GURPTA_DYNAMIC_LIBRARY_HEADER - // define BUILD_GURPTA when you are creating the dynamic library and - // define DYNAMIC_HOOPLE when you are importing code from the dynamic library. - #include - #if defined(BUILD_GURPTA) - #define GURPTA_LIBRARY HOOPLE_DYNAMIC_EXPORT - #elif defined(DYNAMIC_HOOPLE) - #define GURPTA_LIBRARY HOOPLE_DYNAMIC_IMPORT - #else - #define GURPTA_LIBRARY - #endif - #endif // outer guard. -#endif - -#ifdef __WIN32__ - #define HOOPLE_DYNAMIC_EXPORT __declspec(dllexport) - #define HOOPLE_DYNAMIC_IMPORT __declspec(dllimport) -#else - // no known requirements for these tags, so set them to nothing. - #define HOOPLE_DYNAMIC_EXPORT - #define HOOPLE_DYNAMIC_IMPORT -#endif - -#endif // outer guard. - diff --git a/core/library/application/callstack_tracker.cpp b/core/library/application/callstack_tracker.cpp deleted file mode 100644 index c9f43bab..00000000 --- a/core/library/application/callstack_tracker.cpp +++ /dev/null @@ -1,272 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : callstack_tracker * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#ifdef ENABLE_CALLSTACK_TRACKING - -// note: this object cannot be constructed when the memory_checker is still -// tracking memory leaks. it must be disabled so that this object can -// construct without being tracked, which causes an infinite loop. the code -// in the basis extern support takes care of that for us. - -#include "callstack_tracker.h" - -#include -#include - -////#undef new -//is this right way to clean that out. - -const int MAX_STACK_DEPTH = 2000; - // beyond that many stack frames, we will simply refuse to add any more. - -const int MAX_TEXT_FIELD = 1024; - // the most space we allow the class, function, and file to take up. - -const char *emptiness_note = "Empty Stack\n"; - //!< what we show when the stack is empty. - -////////////// - -class callstack_records -{ -public: - frame_tracking_instance _records[MAX_STACK_DEPTH + 2]; // fudging room. -}; - -////////////// - -// our current depth gives us our position in the array. we define our -// stack as starting at element zero, which is a null stack entry and -// corresponds to a depth of zero also. then, when the stack depth is one, -// we actually have an element in place and it resides at index 1. this -// scheme allows us to have an update to the line number just do nothing when -// there is no current stack. - -callstack_tracker::callstack_tracker() -: _bt(new callstack_records), - _depth(0), - _frames_in(0), - _frames_out(0), - _highest(0), - _unusable(false) -{ -//printf("callstack ctor\n"); -} - -callstack_tracker::~callstack_tracker() -{ -//printf("!!!!!!!!!!!!!!!!!! callstack dtor in\n"); - _unusable = true; - WHACK(_bt); -//printf("!!!!!!!!!!!!!!!!!! callstack dtor out\n"); -} - -bool callstack_tracker::push_frame(const char *class_name, const char *func, - const char *file, int line) -{ -//printf("callstack pushframe depth=%d in\n", _depth); - if (_unusable) return false; - if (_depth >= MAX_STACK_DEPTH) { - // too many frames already. - printf("callstack_tracker::push_frame: past limit at class=%s func=%s " - "file=%s line=%d\n", class_name, func, file, line); - return false; - } - _depth++; - if (_depth > _highest) _highest = _depth; - _frames_in += 1; - _bt->_records[_depth].assign(class_name, func, file, line); -//printf("callstack pushframe depth=%d out\n", _depth); - return true; -} - -bool callstack_tracker::pop_frame() -{ -//printf("callstack popframe depth=%d in\n", _depth); - if (_unusable) return false; - if (_depth <= 0) { - // how inappropriate of them; we have no frames. - _depth = 0; // we don't lose anything useful by forcing it to be zero. - printf("callstack_tracker::pop_frame stack underflow!\n"); - return false; - } - _bt->_records[_depth].clean(); - _depth--; - _frames_out += 1; -//printf("callstack popframe depth=%d out\n", _depth); - return true; -} - -bool callstack_tracker::update_line(int line) -{ - if (_unusable) return false; - if (!_depth) return false; // not as serious, but pretty weird. - _bt->_records[_depth]._line = line; - return true; -} - -char *callstack_tracker::full_trace() const -{ - if (_unusable) return strdup(""); -//printf("fulltrace in\n"); - char *to_return = (char *)malloc(full_trace_size()); - to_return[0] = '\0'; - if (!_depth) { - strcat(to_return, emptiness_note); - return to_return; - } - const int initial_len = MAX_TEXT_FIELD + 8; - char temp[initial_len]; - int allowed_len = initial_len; - // space provided for one text line. - // start at top most active frame and go down towards bottom most. - for (int i = _depth; i >= 1; i--) { - strcat(to_return, "\t"); // we left space for this and \n at end. - temp[0] = '\0'; - int len_class = strlen(_bt->_records[i]._class); - int len_func = strlen(_bt->_records[i]._func); - if (allowed_len > len_class + len_func + 6) { - allowed_len -= len_class + len_func + 6; - sprintf(temp, "\"%s::%s\", ", _bt->_records[i]._class, - _bt->_records[i]._func); - strcat(to_return, temp); - } - - temp[0] = '\0'; - int len_file = strlen(_bt->_records[i]._file); - if (allowed_len > len_file + 4) { - allowed_len -= len_file + 4; - sprintf(temp, "\"%s\", ", _bt->_records[i]._file); - strcat(to_return, temp); - } - - temp[0] = '\0'; - sprintf(temp, "\"line=%d\"", _bt->_records[i]._line); - int len_line = strlen(temp); - if (allowed_len > len_line) { - allowed_len -= len_line; - strcat(to_return, temp); - } - - strcat(to_return, "\n"); // we left space for this already. - } - -//printf("fulltrace out\n"); - return to_return; -} - -int callstack_tracker::full_trace_size() const -{ - if (_unusable) return 0; - if (!_depth) return strlen(emptiness_note) + 14; // liberal allocation. - int to_return = 28; // another hollywood style excess. - for (int i = _depth; i >= 1; i--) { - int this_line = 0; // add up parts for just this item. - - // all of these additions are completely dependent on how it's done above. - - int len_class = strlen(_bt->_records[i]._class); - int len_func = strlen(_bt->_records[i]._func); - this_line += len_class + len_func + 6; - - int len_file = strlen(_bt->_records[i]._file); - this_line += len_file + 4; - - this_line += 32; // extra space for line number and such. - - // limit it like we did above; we will use the lesser size value. - if (this_line < MAX_TEXT_FIELD + 8) to_return += this_line; - else to_return += MAX_TEXT_FIELD + 8; - } - return to_return; -} - -////////////// - -frame_tracking_instance::frame_tracking_instance(const char *class_name, - const char *func, const char *file, int line, bool add_frame) -: _frame_involved(add_frame), - _class(class_name? strdup(class_name) : NIL), - _func(func? strdup(func) : NIL), - _file(file? strdup(file) : NIL), - _line(line) -{ - if (_frame_involved) { -//printf("frametrackinst ctor in class=%s func=%s\n", class_name, func); - program_wide_stack_trace().push_frame(class_name, func, file, line); -//printf("frametrackinst ctor out\n"); - } -} - -frame_tracking_instance::frame_tracking_instance - (const frame_tracking_instance &to_copy) -: _frame_involved(false), // copies don't get a right to this. - _class(to_copy._class? strdup(to_copy._class) : NIL), - _func(to_copy._func? strdup(to_copy._func) : NIL), - _file(to_copy._file? strdup(to_copy._file) : NIL), - _line(to_copy._line) -{ -} - -frame_tracking_instance::~frame_tracking_instance() { clean(); } - -void frame_tracking_instance::clean() -{ - if (_frame_involved) { -//printf("frametrackinst clean\n"); - program_wide_stack_trace().pop_frame(); - } - _frame_involved = false; - free(_class); _class = NIL; - free(_func); _func = NIL; - free(_file); _file = NIL; - _line = 0; -} - -frame_tracking_instance &frame_tracking_instance::operator = - (const frame_tracking_instance &to_copy) -{ -//printf("frametrackinst tor = in\n"); - if (this == &to_copy) return *this; - assign(to_copy._class, to_copy._func, to_copy._file, to_copy._line); -//printf("frametrackinst tor = out\n"); - return *this; -} - -void frame_tracking_instance::assign(const char *class_name, const char *func, - const char *file, int line) -{ - clean(); - _frame_involved = false; // copies don't get a right to this. - _class = class_name? strdup(class_name) : NIL; - _func = func? strdup(func) : NIL; - _file = file? strdup(file) : NIL; - _line = line; -} - -void update_current_stack_frame_line_number(int line) -{ -//printf("frametrackinst updatelinenum in\n"); - program_wide_stack_trace().update_line(line); -//printf("frametrackinst updatelinenum out\n"); -} - -#endif // ENABLE_CALLSTACK_TRACKING - - - - diff --git a/core/library/application/callstack_tracker.h b/core/library/application/callstack_tracker.h deleted file mode 100644 index bb75adaa..00000000 --- a/core/library/application/callstack_tracker.h +++ /dev/null @@ -1,156 +0,0 @@ -#ifndef CALLSTACK_TRACKER_CLASS -#define CALLSTACK_TRACKER_CLASS - -/*****************************************************************************\ -* * -* Name : callstack_tracker * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "definitions.h" - -#ifdef ENABLE_CALLSTACK_TRACKING - -#include "build_configuration.h" -#include "root_object.h" - -namespace application { - -// forward. -class callstack_records; -class callstack_tracker; - -////////////// - -callstack_tracker BASIS_EXTERN &program_wide_stack_trace(); - //!< a global object that can be used to track the runtime callstack. - -////////////// - -//! This object can provide a backtrace at runtime of the invoking methods. -/*! - The callstack tracking is hooked in through the FUNCDEF macros used to - set function names for logging. Thus it will only be visible if those - macros are used fairly carefully or if people invoke the stack frame addition - method themselves. -*/ - -class callstack_tracker -{ -public: - callstack_tracker(); - virtual ~callstack_tracker(); - - DEFINE_CLASS_NAME("callstack_tracker"); - - bool push_frame(const char *class_name, const char *func, const char *file, - int line); - //!< adds a new stack from for the "class_name" in "function" at the "line". - /*!< this function should be invoked when entering a new stack frame. the - "file" can be gotten from the __FILE__ macro and the "line" number can come - from __LINE__, but the "class_name" and "func" must be tracked some other - way. we recommend the FUNCDEF macro. this function might return false if - there is no longer any room for tracking more frames; that is a serious - issue that might indicate a runaway recursion or infinite loop. */ - - bool pop_frame(); - //!< removes the last callstack frame off from our tracking. - - bool update_line(int line); - //!< sets the line number within the current stack frame. - /*!< the current frame can reside across several line numbers, so this - allows the code to be more specific about the location of an invocation. */ - - char *full_trace() const; - //!< provides the current stack trace in a newly malloc'd string. - /*!< the user *must* free() the string returned. */ - - int full_trace_size() const; - //!< this returns the number of bytes needed for the above full_trace(). - - int depth() const { return _depth; } - //!< the current number of frames we know of. - - double frames_in() const { return _frames_in; } - //!< reports the number of call stack frames that were added, total. - - double frames_out() const { return _frames_out; } - //!< reports the number of call stack frames that were removed, total. - - double highest() const { return _highest; } - //!< reports the maximum stack depth seen during the runtime so far. - -private: - callstack_records *_bt; //!< the backtrace records for current program. - int _depth; //!< the current number of frames we know of. - double _frames_in; //!< number of frame additions. - double _frames_out; //!< number of frame removals. - double _highest; //!< the most number of frames in play at once. - bool _unusable; //!< object has already been destroyed. -}; - -////////////// - -//! a small object that represents a stack trace in progress. -/*! the object will automatically be destroyed when the containing scope -exits. this enables a users of the stack tracker to simply label their -function name and get the frame added. if they want finer grained tracking, -they should update the line number periodically through their function, -especially when memory is about to be allocated or where something might go -wrong. */ - -class frame_tracking_instance -{ -public: - // these are not encapsulated, but be careful with the contents. - bool _frame_involved; //!< has this object been added to the tracker? - char *_class, *_func, *_file; //!< newly allocated copies. - int _line; - - frame_tracking_instance(const char *class_name = "", const char *func = "", - const char *file = "", int line = 0, bool add_frame = false); - //!< as an automatic variable, this can hang onto frame information. - /*!< if "add_frame" is true, then this actually adds the stack frame in - question to the tracker. thus if you use this class at the top of your - function, such as via the FUNCDEF macro, then you can forget about having - to pop the frame later. */ - - frame_tracking_instance(const frame_tracking_instance &to_copy); - - ~frame_tracking_instance(); - //!< releases the information *and* this stack frame in the tracker. - - frame_tracking_instance &operator =(const frame_tracking_instance &to_copy); - - void assign(const char *class_name, const char *func, const char *file, - int line); - //!< similar to assignment operator but doesn't require an object. - - void clean(); - //!< throws out our accumulated memory and pops frame if applicable. -}; - -void update_current_stack_frame_line_number(int line); - //!< sets the line number for the current frame in the global stack trace. - -#else // ENABLE_CALLSTACK_TRACKING - // bogus replacements for most commonly used callstack tracking support. - #define frame_tracking_instance - #define __trail_of_function(p1, p2, p3, p4, p5) if (func) {} - // the above actually trades on the name of the object we'd normally - // define. it must match the object name in the FUNCDEF macro. - #define update_current_stack_frame_line_number(line) -#endif // ENABLE_CALLSTACK_TRACKING - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/application/command_line.cpp b/core/library/application/command_line.cpp deleted file mode 100644 index 19f61810..00000000 --- a/core/library/application/command_line.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/*****************************************************************************\ -* * -* Name : command_line * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "command_line.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; - -namespace application { - -DEFINE_ARGC_AND_ARGV; - -command_parameter::command_parameter(parameter_types type) -: _type(type), _text(new astring) {} - -command_parameter::command_parameter(parameter_types type, const astring &text) -: _type(type), _text(new astring(text)) {} - -command_parameter::command_parameter(const command_parameter &to_copy) -: _type(VALUE), _text(new astring) -{ *this = to_copy; } - -command_parameter::~command_parameter() { WHACK(_text); } - -const astring &command_parameter::text() const { return *_text; } - -void command_parameter::text(const astring &new_text) { *_text = new_text; } - -command_parameter &command_parameter::operator = - (const command_parameter &to_copy) -{ - if (this == &to_copy) return *this; - _type = to_copy._type; - *_text = *to_copy._text; - return *this; -} - -////////////// - -// option_prefixes: the list of valid prefixes for options on a command line. -// these are the characters that precede command line arguments. For Unix, -// the default is a dash (-), while for DOS most programs use forward-slash -// (/). Adding more characters is trivial; just add a character to the list -// before the sentinel of '\0'. -#if defined(_MSC_VER) || defined(__MINGW32__) - static char option_prefixes[] = { '-', '/', '\0' }; -#elif defined(__UNIX__) - static char option_prefixes[] = { '-', '\0' }; -#else - #error "I don't know what kind of operating system this is." -#endif - -bool it_is_a_prefix_char(char to_test) -{ - for (int i = 0; option_prefixes[i]; i++) - if (to_test == option_prefixes[i]) return true; - return false; -} - -////////////// - -class internal_cmd_line_array_of_parms : public array {}; - -////////////// - -SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, ) - // our default return for erroneous indices. - -command_line::command_line(int argc, char *argv[]) -: _implementation(new internal_cmd_line_array_of_parms), - _program_name(new filename(directory::absolute_path(argv[0]))) -{ - argv++; // skip command name in argv. - - // loop over the rest of the fields and examine them. - string_array string_list; // accumulated below. - while (--argc > 0) { - astring to_store = argv[0]; // retrieve the current string. - string_list += to_store; // put the string in our list. - argv++; // next string. - } - parse_string_array(string_list); -} - -command_line::command_line(const astring &full_line) -: _implementation(new internal_cmd_line_array_of_parms), - _program_name(new filename) -{ - astring accumulator; - string_array string_list; - bool in_quote = false; -//hmmm: this is not quote right yet. -// use the separate command line method, but get it to run iteratively -// so we can keep pulling them apart? maybe it already does! -// separate is better because it handles escaped quotes. - for (int i = 0; i < full_line.length(); i++) { - char to_examine = full_line.get(i); - if (to_examine == '"') { - // it's a quote character, so maybe we can start eating spaces. - if (!in_quote) { - in_quote = true; - continue; // eat the quote character but change modes. - } - // nope, we're closing a quote. we assume that the quotes are - // around the whole argument. that's the best win32 can do at least. - in_quote = false; - to_examine = ' '; // trick parser into logging the accumulated string. - // intentional fall-through to space case. - } - - if (parser_bits::white_space(to_examine)) { - // if this is a white space, then we start a new string. - if (!in_quote && accumulator.t()) { - // only grab the accumulator if there are some contents. - string_list += accumulator; - accumulator = ""; - } else if (in_quote) { - // we're stuffing the spaces into the string since we're quoted. - accumulator += to_examine; - } - } else { - // not white space, so save it in the accumulator. - accumulator += to_examine; - } - } - if (accumulator.t()) string_list += accumulator; - // that partial string wasn't snarfed during the loop. - // grab the program name off the list so the parsing occurs as expected. - *_program_name = directory::absolute_path(string_list[0]); - string_list.zap(0, 0); - parse_string_array(string_list); -} - -command_line::~command_line() -{ - WHACK(_program_name); - WHACK(_implementation); -} - -int command_line::entries() const { return _implementation->length(); } - -filename command_line::program_name() const { return *_program_name; } - -const command_parameter &command_line::get(int field) const -{ - bounds_return(field, 0, entries() - 1, cmdline_blank_parm()); - return _implementation->get(field); -} - -void command_line::separate_command_line(const astring &cmd_line, - astring &app, astring &parms) -{ - char to_find = ' '; // the command separator. - if (cmd_line[0] == '\"') to_find = '\"'; - // if the first character is a quote, then we are seeing a quoted phrase - // and need to look for its completing quote. otherwise, we'll just look - // for the next space. - - int seek_posn = 1; // skip the first character. we have accounted for it. - // skim down the string, looking for the ending of the first phrase. - while (seek_posn < cmd_line.length()) { - // look for our parameter separator. this will signify the end of the - // first phrase / chunk. if we don't find it, then it should just mean - // there was only one item on the command line. - int indy = cmd_line.find(to_find, seek_posn); - if (negative(indy)) { - // yep, there wasn't a matching separator, so we think this is just - // one chunk--the app name. - app = cmd_line; - break; - } else { - // now that we know where our separator is, we need to find the right - // two parts (app and parms) based on the separator character in use. - if (to_find == '\"') { - // we are looking for a quote character to complete the app name. - if (cmd_line[indy - 1] == '\\') { - // we have a backslash escaping this quote! keep seeking. - seek_posn = indy + 1; - continue; - } - app = cmd_line.substring(0, indy); - parms = cmd_line.substring(indy + 2, cmd_line.end()); - // skip the quote and the obligatory space character after it. - break; - } else { - // simple space handling here; no escapes to worry about. - app = cmd_line.substring(0, indy - 1); - parms = cmd_line.substring(indy + 1, cmd_line.end()); - break; - } - } - } -} - -bool command_line::zap(int field) -{ - bounds_return(field, 0, entries() - 1, false); - _implementation->zap(field, field); - return true; -} - -// makes a complaint about a failure and sets the hidden commands to have a -// bogus entry so they aren't queried again. -#define COMPLAIN_CMDS(s) \ - listo_cmds += "unknown"; \ - COMPLAIN(s) - -string_array command_line::get_command_line() -{ -// FUNCDEF("get_command_line"); - string_array listo_cmds; - // the temporary string below can be given a flat formatting of the commands - // and it will be popped out into a list of arguments. - astring temporary; -#ifdef __UNIX__ - if (!_global_argc || !_global_argv) { - // our global parameters have not been set, so we must calculate them. - temporary = application_configuration::get_cmdline_from_proc(); - } else { - // we have easy access to command line arguments supposedly, so use them. - for (int i = 0; i < _global_argc; i++) { - // add a string entry for each argument. - listo_cmds += _global_argv[i]; - } - // we don't need a long string to be parsed; the list is ready. - return listo_cmds; - } -#elif defined(__WIN32__) - // we have easy access to the original list of commands. - for (int i = 0; i < _global_argc; i++) { - // add a string entry for each argument. - listo_cmds += _global_argv[i]; - } - return listo_cmds; -#else - COMPLAIN_CMDS("this OS doesn't support getting the command line."); - return listo_cmds; -#endif - - // now that we have our best guess at a flat representation of the command - // line arguments, we'll chop it up. - -//hmmm: this algorithm doesn't support spaces in filenames currently. -//hmmm: for windows, we can parse the quotes that should be around cmd name. -//hmmm: but for unix, the ps command doesn't support spaces either. how to -// get around that to support programs with spaces in the name? - int posn = 0; - int last_posn = -1; - while (posn < temporary.length()) { - posn = temporary.find(' ', posn); - if (non_negative(posn)) { - // found another space to turn into a portion of the command line. - listo_cmds += temporary.substring(last_posn + 1, posn - 1); - // grab the piece of string between the point just beyond where we - // last saw a space and the position just before the space. - last_posn = posn; // save the last space position. - posn++; // push the pointer past the space. - } else { - // no more spaces in the string. grab what we can from the last bit - // of the string that we see. - if (last_posn < temporary.length() - 1) { - // there's something worthwhile grabbing after the last place we - // saw a space. - listo_cmds += temporary.substring(last_posn + 1, - temporary.length() - 1); - } - break; // we're done finding spaces. - } - } - - return listo_cmds; -} - -astring command_line::text_form() const -{ - astring to_return; - const astring EOL = parser_bits::platform_eol_to_chars(); - for (int i = 0; i < entries(); i++) { - const command_parameter &curr = get(i); - to_return += a_sprintf("%d: ", i + 1); - switch (curr.type()) { - case command_parameter::CHAR_FLAG: - to_return += astring(" ") + curr.text() + EOL; - break; - case command_parameter::STRING_FLAG: - to_return += astring(" ") + curr.text() + EOL; - break; - case command_parameter::VALUE: // pass through to default. - default: - to_return += astring(" ") + curr.text() + EOL; - break; - } - } - return to_return; -} - -bool command_line::find(char option_character, int &index, - bool case_sense) const -{ - astring opt(option_character, 1); // convert to a string once here. - if (!case_sense) opt.to_lower(); // no case-sensitivity. - for (int i = index; i < entries(); i++) { -//hmmm: optimize this too. - if (get(i).type() == command_parameter::CHAR_FLAG) { - bool success = (!case_sense && get(i).text().iequals(opt)) - || (case_sense && (get(i).text() == opt)); - if (success) { - // the type is appropriate and the value is correct as well... - index = i; - return true; - } - } - } - return false; -} - -bool command_line::find(const astring &option_string, int &index, - bool case_sense) const -{ - FUNCDEF("find"); -if (option_string.length() && (option_string[0] == '-') ) -LOG(astring("found option string with dash! string is: ") + option_string); - - for (int i = index; i < entries(); i++) { - if (get(i).type() == command_parameter::STRING_FLAG) { - bool success = (!case_sense && get(i).text().iequals(option_string)) - || (case_sense && (get(i).text() == option_string)); - if (success) { - // the type is appropriate and the value is correct as well... - index = i; - return true; - } - } - } - return false; -} - -bool command_line::get_value(char option_character, astring &value, - bool case_sense) const -{ - value = ""; - int posn = 0; // where we find the flag. - if (!find(option_character, posn, case_sense)) return false; - - // get the value after the flag, if there is such. - posn++; // this is where we think our flag's value lives. - if (posn >= entries()) return false; - - // there's still an entry after where we found our flag; grab it. - command_parameter cp = get(posn); - if (cp.type() != command_parameter::VALUE) return false; - - // finally; we've found an appropriate text value. - value = cp.text(); - return true; -} - -bool command_line::get_value(const astring &option_string, astring &value, - bool case_sense) const -{ - FUNCDEF("get_value"); -if (option_string.length() && (option_string[0] == '-') ) -LOG(astring("found option string with dash! string is: ") + option_string); - - value = ""; - int posn = 0; // where we find the flag. - if (!find(option_string, posn, case_sense)) return false; - - // get the value after the flag, if there is such. - posn++; // this is where we think our flag's value lives. - if (posn >= entries()) return false; - - // there's still an entry after where we found our flag; grab it. - command_parameter cp = get(posn); - if (cp.type() != command_parameter::VALUE) return false; - - // finally; we've found an appropriate text value. - value = cp.text(); - return true; -} - -void command_line::parse_string_array(const string_array &to_parse) -{ - bool still_looking_for_flags = true; // goes to false when only values left. - // loop over the fields and examine them. - for (int i = 0; i < to_parse.length(); i++) { - // retrieve a character from the current string. - int index = 0; - char c = to_parse[i].get(index++); - // we check whether it's a prefix character, and if so, what kind. - if (still_looking_for_flags && it_is_a_prefix_char(c)) { - // at least one prefix is there, so treat this as a flag. - bool gnu_type_of_flag = false; - if (it_is_a_prefix_char(to_parse[i].get(index))) { - // there's a special GNU double flag beginner. - index++; // skip that extra one. - if ( (index >= to_parse[i].length()) - || parser_bits::white_space(to_parse[i].get(index))) { - // special case of '--' (or '//' i suppose) with white space or - // nothing else afterwards; indicates that the rest of the items - // should just be values, not flags. - still_looking_for_flags = false; - continue; // we ate that item. - } - gnu_type_of_flag = true; - } - // everything after the prefixes is considered part of the flag; they're - // either individual flag characters (on a single prefix) or they're the - // full name for the flag (gnu style). - c = 1; // reset to a true bool value. - astring gnu_accumulator; // if processing a gnu flag, it arrives here. - while (c) { - if (!gnu_type_of_flag) { - // add as many flag parameters as possible. - c = to_parse[i].get(index++); - // c will be zero once we hit the end of the string. - if (c) { - command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1)); - *_implementation += to_add; - } - } else { - // the gnu flag name is added to here. - c = to_parse[i].get(index++); // zero at end of string. - if (c) - gnu_accumulator += c; // one more character. - } - } - if (gnu_accumulator.t()) { - // we've accumulated a gnu flag, so store it. - command_parameter to_add(command_parameter::STRING_FLAG, - gnu_accumulator); - *_implementation += to_add; - } - } else { - // add a value type of command_parameter. - astring found = to_parse[i]; - command_parameter to_add(command_parameter::VALUE, found); - *_implementation += to_add; - } - } -} - -astring command_line::gather(int &index) const -{ - astring to_return; - for (int i = index; i < entries(); i++) { - if (get(i).type() == command_parameter::CHAR_FLAG) { - index = i; - return to_return; - } else to_return += get(i).text(); - } - index = entries() - 1; - return to_return; -} - -} //namespace. - diff --git a/core/library/application/command_line.h b/core/library/application/command_line.h deleted file mode 100644 index 989aac5d..00000000 --- a/core/library/application/command_line.h +++ /dev/null @@ -1,219 +0,0 @@ -#ifndef COMMAND_LINE_CLASS -#define COMMAND_LINE_CLASS - -/*****************************************************************************\ -* * -* Name : command_line * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace application { - -//! This class parses the command line passed to the main() function. -/*! - The main function might be called WinMain or be implemented by CWinApp in MS-Windows, but all - of these types of applications will still have flags passed to them. This class constructs a - list of parameters from the command line provided by the OS. A parameter is either a command - flag or a value string. Flag characters take the form of "-l" or "-Q" or "-als" (which has - three flags). Value strings are other strings found on the command line that do not start with - the separator character (usually '-'). Flag characters are all broken up into separate entries. - A special kind of flag uses a double separator to allow multiple character flag names (e.g., - the string "--follow" where the flag name is "follow"), as in GNU software. - This class knows about the convention of a paramter of just '--' being used to - indicate that the rest of the line is all normal parameters and has no intentional flags - in them. This allows passing of character strings that would otherwise be misinterpreted - as flags rather than literal input. - -(not implemented) - A special feature is provided to allow parsing of flag characters followed - directly by the value (as in "-fbroiler.h", where the flag character is 'f' - and the value is "broiler.h". -(not implemented) -*/ - -// forward declarations. -class internal_cmd_line_array_of_parms; - -class command_parameter : public virtual basis::root_object -{ -public: - enum parameter_types { VALUE, CHAR_FLAG, STRING_FLAG, BOGUS_ITEM }; - - command_parameter(parameter_types type = BOGUS_ITEM); - //!< default constructor initializes to mostly blank state. - - command_parameter(parameter_types type, const basis::astring &text); - //!< constructs a parameter of "type" where the value is "text". - /*!< if the "type" is CHAR_FLAG, then this should be a string of - one character. for STRING_FLAG, the length is arbitrary. */ - - command_parameter(const command_parameter &to_copy); - - ~command_parameter(); - - DEFINE_CLASS_NAME("command_parameter"); - - command_parameter &operator =(const command_parameter &to_copy); - - parameter_types type() const { return _type; } - //!< observes the type of the parameter. - void type(parameter_types new_type) { _type = new_type; } - //!< modifies the type of the parameter. - - const basis::astring &text() const; - //!< observes the string contents. - void text(const basis::astring &new_text); - //!< modifies the string contents. - -private: - parameter_types _type; - basis::astring *_text; -}; - -////////////// - -class command_line -{ -public: - command_line(int argc, char *argv[]); - //!< takes command line parameters in the form of "argc" and "argv". - /*!< this is suitable for most C++ main programs. the first "argv" string (element zero) is - ignored because it is assumed that it is the program name. that means that the array of - command parameters here will be (argc - 1) in length, and that index zero of our array has - the first "real" parameter that was passed to the program (i.e., not it's name). - note that the unaltered command parameters of argc and argv become available in the global - variables _global_argc and _global_argv. */ - - command_line(const basis::astring &to_parse); - //!< takes a string form of the command line. - /*!< this is the form rendered by GetCommandLine() in Win32. on certain - win32 platforms, this may not return a full path for the program_name() - method. this uses the separate_command_line() method to pick out the - relevant pieces and supports embedded, escaped quotes. */ - - virtual ~command_line(); - - DEFINE_CLASS_NAME("command_line"); - - filesystem::filename program_name() const; - //!< Returns the program name found in the command line. - - static void separate_command_line(const basis::astring &cmd_line, basis::astring &app, - basis::astring &parms); - //!< breaks apart a command line in "cmd_line" into "app" and "parms". - /*!< when given a full command line, where the application to run is the - first chunk and its parameters (if any) are subsequent chunks, this will - store the application name in "app" and the rest of the parameters in - "parms". this expects any paths in the "cmd_line" that contain spaces - to be surrounded by quotes. if there are any quote characters that are - escaped, they are considered to be embedded in the parameter string; they - will not be considered as matching any pending closing quotes. */ - - int entries() const; - //!< Returns the number of fields found on the command line. - /*!< This does not include the program name found; that's only - accessible through the program_name() method. */ - - const command_parameter &get(int field) const; - //!< Returns the parameter at the "field" specified. - /*!< The "field" ranges from zero through "entries() - 1" inclusive. if - an invalid index is used, then the type will be BOGUS_ITEM. */ - - bool zap(int field); - //!< eats the entry at position "field". - /*!< this is useful for cleaning out entries that have already been dealt - with. */ - - // note: in the following, if "case_sense" is true, then the searches are - // case-sensitive. otherwise, case of the flags is not a concern. - // the returned values always retain the original case. - - bool find(char option_character, int &index, bool case_sense = true) const; - //!< Returns true if the "option_character" is found in the parameters. - /*!< The search starts at the "index" specified, and if the item is found, - its location is returned in "index" and the function returns true. - Otherwise false is returned and the "index" is not modified. */ - bool find(const basis::astring &option_string, int &index, - bool case_sense = true) const; - //!< Returns true if the "option_string" is found in the parameters. - - bool get_value(char option_character, basis::astring &value, - bool case_sense = true) const; - //!< retrieves the "value" found for the option flag specified. - /*!< this is useful for command lines with standard spacing. for example, - if the command line is "-Q query.bop --Linkage plorgs.txt", then this - function would return "query.bop" for a search on 'Q' and the find() - method below would return "plorgs.txt" for the string flag search on - "Linkage". */ - bool get_value(const basis::astring &option_string, basis::astring &value, - bool case_sense = true) const; - //!< retrieves the "value" found for the "option_string" specified. - -//is this useful? it's kind of like what we need for special flags (like -// -fgob.h, where gob.h is a value parameter) but needs to terminate -//differently for that to work. - basis::astring gather(int &index) const; - //!< coalesces parameters together until the next option flag. - /*!< Returns a string constructed from the concatenation of the strings - for the parameters at all indices in the list starting at "index" until - an option character is found. Note that this means an empty string - will be returned if the parameter at "index" has an option character, - or if "index" is greater than or equal to "elements()". - After gather, "index" is set to the last location included in the - string. "index" is set to the last index in the list if "index" was - past the end to begin with or if strings are gathered up to the last - index. otherwise, "index" is unchanged if nothing was gathered. */ - - basis::astring text_form() const; - //!< returns a string with all the information we have for the command line. - - static structures::string_array get_command_line(); - //!< returns the command line passed to the program as a list of strings. - /*!< the string at index zero is the program name. this is just a useful - helper function and is not normally needed by users of the command_line - object. */ - -private: - internal_cmd_line_array_of_parms *_implementation; //!< held parameters. - filesystem::filename *_program_name; //!< the name of this program. - - void parse_string_array(const structures::string_array &to_parse); - //!< pulls all the strings in "to_parse" into the command_parameter list. - - // forbidden: - command_line(const command_line &to_copy); - command_line &operator =(const command_line &to_copy); - - static const command_parameter &cmdline_blank_parm(); -}; - -////////////// - -// this declares a program-wide command-line argument storage area. - -extern int _global_argc; -extern char **_global_argv; -//! this macro allocates space for the command-line storage areas. -#define DEFINE_ARGC_AND_ARGV int _global_argc = 0; char **_global_argv = NIL - -//! this macro assigns our command-line parameters for this program. -#define SET_ARGC_ARGV(argc, argv) { \ - application::_global_argc = argc; \ - application::_global_argv = argv; \ -} - -} //namespace. - -#endif - diff --git a/core/library/application/dll_root.cpp b/core/library/application/dll_root.cpp deleted file mode 100644 index 7eab74b3..00000000 --- a/core/library/application/dll_root.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/*****************************************************************************\ -* * -* Name : DLL Main Root Support * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// Thanks to Andy Tan for some research into MFC extension dlls. - -#include -#include -#include - -//HOOPLE_STARTUP_CODE_DLL; - // initialize objects needed by the hoople libs. - -#ifdef _AFXDLL - -#ifdef DEBUG - #define TRACE_PRINTER(s) TRACE_PRINT(s) -#else - #define TRACE_PRINTER(s) -#endif - -// base for AFX dlls. - -#include // MFC extensions. -#include // Extension DLL declarations; include only once! - -bool check_DLL_versions(); - // supplied by the library's version of dllmain.cpp. - -static AFX_EXTENSION_MODULE SomeDLL = { NULL, NULL }; - -#ifndef DLL_NAME - #define DLL_NAME "unnamed DLL" -#endif - -extern "C" int APIENTRY -DllMain(application_instance instance, DWORD reason, LPVOID reserved) -{ - SET_INSTANCE_HANDLE(instance); - // Remove this if you use lpReserved. - UNREFERENCED_PARAMETER(reserved); - - char *dll_name = DLL_NAME; - // mainly for debugging purposes. having the value for DLL_NAME actually - // stored should allow us to know which dll is being debugged. - - static CDynLinkLibrary *dll_link = NIL; - - static int dll_entry_count = 0; - - switch (reason) { - case DLL_PROCESS_ATTACH: { - char *message = DLL_NAME " Initializing!\n"; - TRACE_PRINTER(message); - - if (!check_DLL_versions()) return 0; - - // Extension DLL one-time initialization - if (!AfxInitExtensionModule(SomeDLL, instance)) return 0; - - // Insert this DLL into the resource chain. - dll_link = new CDynLinkLibrary(SomeDLL); - - // NOTE: If this Extension DLL is being implicitly linked to by an MFC - // Regular DLL (such as an ActiveX Control) instead of an MFC - // application, then you will want to remove this line from DllMain and - // put it in a separate function exported from this Extension DLL. The - // Regular DLL that uses this Extension DLL should then explicitly call - // that function to initialize this Extension DLL. Otherwise, the - // CDynLinkLibrary object will not be attached to the Regular DLL's - // resource chain, and serious problems will result. - ++dll_entry_count; - break; - } - case DLL_PROCESS_DETACH: { - --dll_entry_count; - char *message = DLL_NAME " Terminating!\n"; - TRACE_PRINTER(message); - // clean up our other stuff. - WHACK(dll_link); - // Terminate the library before destructors are called. - AfxTermExtensionModule(SomeDLL); - break; - } - case DLL_THREAD_ATTACH: - ++dll_entry_count; - break; - case DLL_THREAD_DETACH: - --dll_entry_count; - break; - default: -// do nothing. - break; - } - - return 1; -} - -#elif defined(__WIN32__) - -// regular dll base. - -#include // base windows stuff. - -bool check_DLL_versions(); - // supplied by the library's version of dllmain.cpp. - -BOOL APIENTRY DllMain(HANDLE module, DWORD ul_reason_for_call, LPVOID reserved) -{ - SET_INSTANCE_HANDLE((application_instance)module); - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - if (!check_DLL_versions()) return 0; - break; - - // these are currently not processed. - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return true; -} - -#endif - diff --git a/core/library/application/hoople_main.h b/core/library/application/hoople_main.h deleted file mode 100644 index aac5a74e..00000000 --- a/core/library/application/hoople_main.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef HOOPLE_MAIN_GROUP -#define HOOPLE_MAIN_GROUP - -/*****************************************************************************\ -* * -* Name : HOOPLE_MAIN group -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! @file "hoople_main.h" Provides macros that implement the 'main' program of an application. - -#include "application_shell.h" -#include "command_line.h" -#include "windoze_helper.h" - -#include -#include -#include -#include -#include - -namespace application { - -// The following versions of main programs are provided for different operating -// systems and compilation environments. These support the requirements of the -// HOOPLE startup code and program-wide features, assuming an object derived -// from base_application is available. -// The object derived from base_application must be provided in "obj_name". -// The "obj_args" are any arguments that need to be passed to the object's -// constructor; this list can be empty. -// Note that the default logger used for unix and console mode win32 programs -// is the console logger; that can be changed in the startup code for the -// "obj_name". - -#define HOOPLE_STARTUP_CODE \ - DEFINE_INSTANCE_HANDLE; - -#ifdef __WXWIDGETS__ - //! main program for applications using WxWidgets library. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - int main(int argc, char *argv[]) { \ - SET_ARGC_ARGV(argc, argv); \ - SETUP_COMBO_LOGGER; \ - obj_name to_run_obj obj_args; \ - return to_run_obj.execute_application(); \ - } - -////////////// - -#elif defined(__UNIX__) - //! options that should work for most unix and linux apps. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - int main(int argc, char *argv[]) { \ - SET_ARGC_ARGV(argc, argv); \ - SETUP_COMBO_LOGGER; \ - obj_name to_run_obj obj_args; \ - return to_run_obj.execute_application(); \ - } - -////////////// - -#elif defined(__WIN32__) - // for win32 we need to support four different environments--console mode, - // borland compilation, MFC programs and regular windows programs. - #ifdef _CONSOLE - //! console mode programs can easily write to a command shell. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - int main(int argc, char *argv[]) { \ - SETUP_COMBO_LOGGER; \ - SET_ARGC_ARGV(argc, argv); \ - obj_name to_run_obj obj_args; \ - return to_run_obj.execute_application(); \ - } - #elif defined(_AFXDLL) - //! MFC applications generally use a tiny shell which hooks up logging. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - SET_ARGC_ARGV(__argc, __argv); \ - tiny_application theApp; - #elif defined(__WIN32__) - //! standard win32 applications have no console, so we just log to a file. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - int WINAPI WinMain(application_instance instance, \ - application_instance prev_instance, LPSTR lpCmdLine, \ - int nCmdShow) { \ - SET_ARGC_ARGV(__argc, __argv); \ - SET_INSTANCE_HANDLE(instance); \ - SETUP_FILE_LOGGER; \ - obj_name to_run_obj obj_args; \ - return to_run_obj.execute_application(); \ - } - #endif - -////////////// - -#else // not __UNIX__ or __WIN32__ - //! just guessing this might work; otherwise we have no idea. - #define HOOPLE_MAIN(obj_name, obj_args) \ - HOOPLE_STARTUP_CODE; \ - int main(int argc, char *argv[]) { \ - SETUP_CONSOLE_LOGGER; \ - obj_name to_run_obj obj_args; \ - return to_run_obj.execute_application(); \ - } -#endif - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/application/hoople_service.cpp b/core/library/application/hoople_service.cpp deleted file mode 100644 index 969a2c27..00000000 --- a/core/library/application/hoople_service.cpp +++ /dev/null @@ -1,224 +0,0 @@ -////////////// -// Name : hoople_service -// Author : Chris Koeritz -////////////// -// Copyright (c) 2000-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "hoople_service.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; -using namespace timely; - -//#define DEBUG_HOOPLE_SERVICE - // uncomment for noisy version. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -namespace application { - -////////////// - -SAFE_STATIC(astring, hoople_service::_app_name, ) - -bool &hoople_service::_defunct() { static bool _defu = false; return _defu; } - -bool &hoople_service::_saw_interrupt() -{ static bool _saw = false; return _saw; } - -int &hoople_service::_timer_period() { static int _tim = 0; return _tim; } - -////////////// - -static hoople_service *_global_hoople_service = NIL; - // this static object is set by the setup() method. it should only come - // into existence once during a program's lifetime. - -hoople_service::hoople_service() -{ -} - -hoople_service::~hoople_service() -{ - make_defunct(); - if (_global_hoople_service) { - program_wide_timer().zap_timer(_global_hoople_service); - } -} - -void hoople_service::handle_startup() { /* nothing for base class. */ } - -void hoople_service::handle_shutdown() { /* nothing for base class. */ } - -void hoople_service::handle_timer() { /* nothing for base class. */ } - -void hoople_service::handle_timer_callback() -{ - // don't invoke the user's timer unless the object is still alive. - if (!is_defunct()) handle_timer(); -} - -void hoople_service::close_this_program() -{ - make_defunct(); -} - -bool hoople_service::close_application(const astring &app_name) -{ - FUNCDEF("close_application"); - process_entry_array procs; - process_control querier; - - // lookup process ids of apps. - bool got_list = querier.query_processes(procs); - if (!got_list) { - LOG(astring("couldn't get process list.")); - return false; - } - int_set pids; - got_list = querier.find_process_in_list(procs, app_name, pids); - if (!got_list) { - LOG(astring("couldn't find process in the list of active ones.")); - return true; - } - - // zap all of them using our signal. - for (int i = 0; i < pids.length(); i++) { -//would linux be better served with sigterm also? -#ifdef __UNIX__ - kill(pids[i], SIGHUP); -#endif -#ifdef __WIN32__ -//lame--goes to whole program. - raise(SIGTERM); -#endif -//hmmm: check results... - } - - return true; -} - -void hoople_service::handle_OS_signal(int formal(sig_id)) -{ - _saw_interrupt() = true; // save the status. - if (_global_hoople_service) { - _global_hoople_service->close_this_program(); - } -} - -void hoople_service::make_defunct() -{ - _defunct() = true; -} - -bool hoople_service::setup(const astring &app_name, int timer_period) -{ -//hmmm: make sure not already initted. - - // simple initializations first... - _timer_period() = timer_period; - _app_name() = app_name; - - _global_hoople_service = this; - - // setup signal handler for HUP signal. this is the one used to tell us - // to leave. -#ifdef __UNIX__ - signal(SIGHUP, handle_OS_signal); -#endif - - // setup a handler for interrupt (e.g. ctrl-C) also. - signal(SIGINT, handle_OS_signal); -#ifdef __WIN32__ - signal(SIGBREAK, handle_OS_signal); -#endif - - return true; -} - -bool hoople_service::launch_console(hoople_service &alert, - const astring &app_name, int timer_period) -{ -#ifdef DEBUG_HOOPLE_SERVICE - FUNCDEF("launch_console"); -#endif - if (!alert.setup(app_name, timer_period)) return false; - - alert.handle_startup(); // tell the program it has started up. - - // start a timer if they requested one. - if (_timer_period()) { - program_wide_timer().set_timer(_timer_period(), &alert); - } - -#ifdef DEBUG_HOOPLE_SERVICE - time_stamp next_report(10 * SECOND_ms); -#endif - - while (!alert.is_defunct()) { -#ifdef DEBUG_HOOPLE_SERVICE - if (time_stamp() >= next_report) { - printf("%s: shout out from my main thread yo.\n", _global_argv[0]); - next_report.reset(10 * SECOND_ms); - } -#endif - time_control::sleep_ms(42); - } - alert.handle_shutdown(); - return true; -} - -/* -#ifdef __WIN32__ -bool hoople_service::launch_event_loop(hoople_service &alert, - const astring &app_name, int timer_period) -{ - if (!alert.setup(app_name, timer_period)) return false; - alert.handle_startup(); - - if (timer_period) - program_wide_timer().set_timer(timer_period, this); - - MSG msg; - msg.hwnd = 0; msg.message = 0; msg.wParam = 0; msg.lParam = 0; - while (!alert.is_defunct() && (GetMessage(&msg, NIL, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - alert.handle_shutdown(); - - return true; -} -#endif -*/ - -} //namespace. - diff --git a/core/library/application/hoople_service.h b/core/library/application/hoople_service.h deleted file mode 100644 index 8f02ff19..00000000 --- a/core/library/application/hoople_service.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef HOOPLE_SERVICE_CLASS -#define HOOPLE_SERVICE_CLASS - -////////////// -// Name : hoople_service -// Author : Chris Koeritz -////////////// -// Copyright (c) 2000-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include -#include -#include - -namespace application { - -//! A platform-independent way to alert a program that it should shut down immediately. -/*! - This can provide a service management feature for graceful shutdown of an - application, allowing it a chance to close its objects cleanly, rather than - just being whacked in the middle of whatever it's doing. - Only one of these objects should be instantiated per program, but the - static methods can be used from anywhere in the program. -*/ - -class hoople_service -: public virtual basis::root_object, public timely::timeable -{ -public: - hoople_service(); - //!< constructor does very little; setup() is what begins operation. - - virtual ~hoople_service(); - - DEFINE_CLASS_NAME("hoople_service"); - - bool setup(const basis::astring &app_name, int timer_period = 0); - //!< constructs a hoople_service for the "app_name" specified. - /*!< this can be any string, although it might be processed for certain - operating systems. also, for close_this_program() to work properly, it - must be the application's basename. the "timer_period" specifies how - frequently to invoke the handle_timer() method during runtime. if it's - zero, then no timer will be used. */ - - static bool is_defunct() { return _defunct(); } - //!< returns true if the object has been marked as defunct. - /*!< this means that it is either shutting down or soon will be. */ - - static void make_defunct(); - //!< used by the derived class to mark that this object is about to exit. - /*!< note that this can be used anywhere in the program to initiate an - exit of the program. */ - - bool saw_interrupt() { return _saw_interrupt(); } - //!< reports whether the process saw an interrupt from the user. - - // these virtual methods can be overridden by applications derived from the - // hoople_service. they support a graceful shutdown process by which - // applications can be alerted that they must shutdown, allowing them to take - // care of releasing resources beforehand. - - virtual void handle_startup(); - //!< this function is called once the program has begun operation. - - virtual void handle_shutdown(); - //!< called during the program's shutdown process. - /*!< this is invoked just prior to the destruction of this class which is - also just before the shutdown of the program overall. in this method, - the derived object must ensure that any threads the program started get - stopped, that any opened files get closed, and that any other resources - are released. this is the application's last chance to clean up. */ - - virtual void handle_timer(); - //!< called periodically if a timer period was specified. - - // static methods that can be used by the program for starting up or for - // graceful shutdown. - -//why? - static bool launch_console(hoople_service &alert, const basis::astring &app_name, - int timer_period = 0); - //!< this is used to begin execution of a console mode application. - /*!< this method does not do anything except sit while the extant threads - are in play. it will not return until the program must exit, as caused - by close_this_program() or close_application(). */ - -#if 0 //not implemented. -#ifdef __WIN32__ - static bool launch_event_loop(hoople_service &alert, - const basis::astring &app_name, int timer_period = 0); - //!< launches by starting up a windowing event loop. - /*!< this is appropriate for programs that are windowed and must - continually process window events. */ -#endif -#endif - - static void close_this_program(); - //!< causes this particular application to begin shutting down. - /*!< this is a static method available for programs that support the - hoople_service's graceful shutdown process. it causes the application - to begin the shutdown. */ - - static bool close_application(const basis::astring &app_name); - //!< attempts to close the application named "app_name". - /*!< this can only be done if this program possesses sufficient rights to - zap that program. */ - - // internal methods not to be used by outside objects. - - static void handle_OS_signal(int sig_id); - //!< processes the signal from the OS when its time to shut down. - -private: - static bool &_saw_interrupt(); //!< did we see a break from the user? - static basis::astring &_app_name(); //!< the of this application. - static bool &_defunct(); //!< is the program shutting down? - static int &_timer_period(); //!< rate at which timer goes off. - - virtual void handle_timer_callback(); //!< invoked by the timer driver. - - // not appropriate. - hoople_service(const hoople_service &); - hoople_service &operator =(const hoople_service &); -}; - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/application/launch_manager.cpp b/core/library/application/launch_manager.cpp deleted file mode 100644 index 7e07c1e6..00000000 --- a/core/library/application/launch_manager.cpp +++ /dev/null @@ -1,804 +0,0 @@ -/*****************************************************************************\ -* * -* Name : launch_manager -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "hoople_service.h" -#include "launch_manager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; - -namespace application { - -#define DEBUG_PROCESS_MANAGER - // uncomment for verbose diagnostics. - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -const int CHECK_INTERVAL = 4 * SECOND_ms; - // this is how frequently the checking thread executes to ensure that - // processes are gone when they should be. - -const int GRACEFUL_SLACK = 90 * SECOND_ms; - // the length of time before a graceful shutdown devolves into a forced - // shutdown. - -const int MAXIMUM_INITIAL_APP_WAIT = 4 * SECOND_ms; - // this is the longest we bother to wait for a process we just started. - // if it hasn't begun by then, we decide it will never do so. - -const int STARTUP_APPS_DELAY_PERIOD = 2 * SECOND_ms; - // we delay for this long before the initial apps are started. - -const int MAXIMUM_REQUEST_PAUSE = 42 * SECOND_ms; - // the longest we will ever wait for a response to be generated based on - // our last request. - -// these are concurrency control macros for the lists managed here. -#define LOCK_CONFIG auto_synchronizer l(*_config_lock) -#define LOCK_ZOMBIES auto_synchronizer l(*_zombie_lock) -#define LOCK_KIDS auto_synchronizer l(*_scamp_lock) - -// error messages. -#ifdef DEBUG_PROCESS_MANAGER - #define COMPLAIN_APPLICATION \ - LOG(astring("the application called ") + app_name + " could not be found.") - #define COMPLAIN_PRODUCT \ - LOG(astring("the section for ") + product + " could not be found.") -#else - #define COMPLAIN_APPLICATION {} - #define COMPLAIN_PRODUCT {} -#endif - -////////////// - -class launch_manager_thread : public ethread -{ -public: - launch_manager_thread(launch_manager &parent) - : ethread(CHECK_INTERVAL, ethread::SLACK_INTERVAL), - _parent(parent) {} - virtual ~launch_manager_thread() {} - - virtual void perform_activity(void *) - { _parent.push_timed_activities(_processes); } - -private: - launch_manager &_parent; // the owner of the object. - process_entry_array _processes; // will be filled as needed. -}; - -////////////// - -class graceful_record -{ -public: - astring _product; // the product name the app is listed under. - astring _app_name; // the application's name. - time_stamp _started; // when the graceful shutdown started. - int _pid; // the particular process id for this app. - int _level; // the shutdown ordering specifier. - - graceful_record(int pid = 0, const astring &product = "", - const astring &app_name = "", int level = 0) - : _product(product), _app_name(app_name), _pid(pid), _level(level) {} -}; - -class graceful_array : public array {}; - -////////////// - -launch_manager::launch_manager(configured_applications &config) -: _configs(config), - _started_initial_apps(false), - _checker(new launch_manager_thread(*this)), - _config_lock(new mutex), - _going_down(new graceful_array), - _zombie_lock(new mutex), - _our_kids(new graceful_array), - _scamp_lock(new mutex), - _stop_launching(false), - _startup_time(new time_stamp(STARTUP_APPS_DELAY_PERIOD)), - _procs(new process_control), - _gag_exclusions(new string_set), - _tracking_exclusions(new string_set) -{ -// FUNCDEF("constructor"); - - // start the application checking thread. - _checker->start(NIL); - - _checker->reschedule(200); // make it start pretty quickly. -} - -launch_manager::~launch_manager() -{ - FUNCDEF("destructor"); - stop_everything(); - - WHACK(_checker); - WHACK(_going_down); - WHACK(_our_kids); - WHACK(_scamp_lock); - WHACK(_zombie_lock); - WHACK(_config_lock); - WHACK(_startup_time); - WHACK(_procs); - WHACK(_gag_exclusions); - WHACK(_tracking_exclusions); - LOG("launch_manager is now stopped."); -} - -void launch_manager::add_gag_exclusion(const astring &exclusion) -{ *_gag_exclusions += exclusion; } - -void launch_manager::add_tracking_exclusion(const astring &exclusion) -{ *_tracking_exclusions += exclusion; } - -const char *launch_manager::outcome_name(const outcome &to_name) -{ - switch (to_name.value()) { - case FILE_NOT_FOUND: return "FILE_NOT_FOUND"; - case NO_ANCHOR: return "NO_ANCHOR"; - case NO_PRODUCT: return "NO_PRODUCT"; - case NO_APPLICATION: return "NO_APPLICATION"; - case BAD_PROGRAM: return "BAD_PROGRAM"; - case LAUNCH_FAILED: return "LAUNCH_FAILED"; - case NOT_RUNNING: return "NOT_RUNNING"; - case FROZEN: return "FROZEN"; - default: return common::outcome_name(to_name); - } -} - -void launch_manager::stop_everything() -{ - _stop_launching = true; // at least deny any connected clients. - stop_all_kids(); // shut down all programs that we started. - _checker->stop(); // stop our thread. -} - -void launch_manager::stop_all_kids() -{ - FUNCDEF("stop_all_kids"); - _stop_launching = true; // set this for good measure to keep clients out. - LOG("zapping any active sub-processes prior to exit."); - - // now we wait for the process closures to take effect. we are relying on - // the graceful shutdown devolving to a process zap and the timing is - // rooted around that assumption. - - for (int lev = 100; lev >= 0; lev--) { - // loop from our highest level to our lowest for the shutdown. -#ifdef DEBUG_PROCESS_MANAGER - LOG(a_sprintf("level %d", lev)); -#endif - bool zapped_any = false; - { - // this shuts down all the child processes we've started at this level. - LOCK_KIDS; // lock within this scope. - for (int i = _our_kids->length() - 1; i >= 0; i--) { - // now check each record and see if it's at the appropriate level. - graceful_record &grace = (*_our_kids)[i]; - if (lev == grace._level) { - // start a graceful shutdown. - zap_process(grace._product, grace._app_name, true); - // remove it from our list. - _our_kids->zap(i, i); - zapped_any = true; // set our flag. - } - } - } - int num_dying = 1; // go into the loop once at least. - -#ifdef DEBUG_PROCESS_MANAGER - time_stamp next_print(4 * SECOND_ms); -#endif - - while (num_dying) { -#ifdef DEBUG_PROCESS_MANAGER - if (time_stamp() >= next_print) { - LOG("waiting..."); - next_print.reset(4 * SECOND_ms); - } -#endif - - // while there are any pending process zaps, we will wait here. this - // will hose us but good if the processes aren't eventually cleared up, - // but that shouldn't happen. - - { - LOCK_ZOMBIES; - num_dying = _going_down->length(); - } - - if (!num_dying) break; // jump out of loop. - - _checker->reschedule(0); // make thread check as soon as possible. - - time_control::sleep_ms(40); - } -#ifdef DEBUG_PROCESS_MANAGER - LOG("done waiting..."); -#endif - } -} - -void launch_manager::launch_startup_apps() -{ - FUNCDEF("launch_startup_apps"); - - // read the startup section. - string_table startup_info; - { - LOCK_CONFIG; - if (!_configs.find_section(_configs.STARTUP_SECTION(), startup_info)) { - // if there's no startup section, we do nothing right now. - LOG("the startup section was not found!"); - return; - } - } -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("table has: ") + startup_info.text_form()); -#endif - - for (int i = 0; i < startup_info.symbols(); i++) { - astring app = startup_info.name(i); - if (app.equal_to(configured_applications::STARTUP_APP_NAME())) continue; - // skip bogus name that keeps the section present. - astring info = startup_info[i]; - LOG(astring("launching application ") + app + "..."); - // parse the items that are in the entry for this program. - astring product, parms; - bool one_shot; - if (!configured_applications::parse_startup_entry(info, product, parms, - one_shot)) { - LOG("the startup entry was not malformed; we could not parse it!"); - continue; - } - - LOG(app + a_sprintf(" is %ssingle shot.", one_shot? "" : "not ")); - - // now try to send the program off on its way. - launch_now(product, app, parms); - - if (one_shot) { - // it's only supposed to be started once, so now toss out the entry. - remove_from_startup(product, app); - } - } -} - -outcome launch_manager::launch_now(const astring &product, - const astring &app_name, const astring ¶meters) -{ - FUNCDEF("launch_now"); - LOG(astring("product \"") + product + "\", application \"" + app_name - + (parameters.length() ? astring("\"") - : astring("\", parms=") + parameters)); - - if (_stop_launching) { - // if the application is one of the exceptions to the gag rule, then - // we will still launch it. otherwise, we'll ignore this request. - if (_gag_exclusions->member(app_name)) { - // this is one of the apps that can still be launched when gagged. - } else { - LOG(astring("application \"") + app_name + "\" cannot be launched;" - + parser_bits::platform_eol_to_chars() + "launching services have been " - "shut down."); - return LAUNCH_FAILED; - } - } - - astring entry_found; - int level; - { - LOCK_CONFIG; - - // get the specific entry for the program they want. - entry_found = _configs.find_program(product, app_name, level); - if (!entry_found) { - if (!_configs.product_exists(product)) { - // return more specific error for missing product. - COMPLAIN_PRODUCT; - return NO_PRODUCT; - } - COMPLAIN_APPLICATION; - return NO_APPLICATION; - } - } - - filename existence_check(entry_found); - if (!existence_check.exists()) { - LOG(astring("file or path wasn't found for ") + entry_found + "."); - return FILE_NOT_FOUND; - } - - basis::un_int kid = 0; - int ret = launch_process::run(entry_found, parameters, - launch_process::RETURN_IMMEDIATELY | launch_process::HIDE_APP_WINDOW, kid); - if (!ret) { - // hey, it worked! so now make sure we track its lifetime... - - if (_tracking_exclusions->member(app_name)) { - // this is one of the apps that we don't track. if it's still - // running when we're shutting down, it's not our problem. - return OKAY; - } - - if (kid) { - // we were told the specific id for the process we started. - LOCK_KIDS; - LOG(a_sprintf("adding given process id %d for app %s at level %d.", - kid, app_name.s(), level)); - graceful_record to_add(kid, product, app_name, level); - *_our_kids += to_add; - return OKAY; - } -#ifdef DEBUG_PROCESS_MANAGER - LOG("was not told child process id!!!"); -#endif - // we weren't told the process id; let's see if we can search for it. - int_set pids; - time_stamp give_it_up(MAXIMUM_INITIAL_APP_WAIT); - while (give_it_up > time_stamp()) { - // find the process id for the program we just started. - if (find_process(app_name, pids)) break; - time_control::sleep_ms(10); // pause to see if we can find it yet. - } - - if (time_stamp() >= give_it_up) { - // we could not launch it for some reason, or our querier has failed. - // however, as far as we know, it launched successfully. if the id is - // missing, then that's not really our fault. we will just not be able - // to track the program, possibly because it's already gone. - LOG(astring("no process found for product \"") + product - + "\", application \"" + app_name + "\"; not adding " - "tracking record."); - return OKAY; - } - - LOCK_KIDS; - - // add all the processes we found under that name. - for (int i = 0; i < pids.elements(); i++) { - LOG(a_sprintf("adding process id %d for app %s at level %d.", - pids[i], app_name.s(), level)); - graceful_record to_add(pids[i], product, app_name, level); - *_our_kids += to_add; - } - return OKAY; - } - - // if we reached here, things are not good. we must not have been able to - // start the process. - -#ifdef __WIN32__ - if (ret == NO_ERROR) return OKAY; // how would that happen? - else if ( (ret == ERROR_FILE_NOT_FOUND) || (ret == ERROR_PATH_NOT_FOUND) ) { - LOG(astring("file or path wasn't found for ") + app_name + "."); - return FILE_NOT_FOUND; - } else if (ret == ERROR_BAD_FORMAT) { - LOG(astring(app_name) + " was not in EXE format."); - return BAD_PROGRAM; - } else { - LOG(astring("there was an unknown error while trying to run ") - + app_name + "; the error is:" + parser_bits::platform_eol_to_chars() - + critical_events::system_error_text(ret)); - return LAUNCH_FAILED; - } -#else - LOG(astring("error ") + critical_events::system_error_text(ret) - + " occurred attempting to run: " + app_name + " " + parameters); - return FILE_NOT_FOUND; -#endif -} - -outcome launch_manager::launch_at_startup(const astring &product, - const astring &app_name, const astring ¶meters, int one_shot) -{ - FUNCDEF("launch_at_startup"); - LOCK_CONFIG; // this whole function modifies the config file. - -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("product \"") + product + "\", application \"" + app_name - + (one_shot? astring("\", OneShot") : astring("\", MultiUse"))); -#endif - - // get the specific entry for the program they want. - int level; - astring entry_found = _configs.find_program(product, app_name, level); - if (!entry_found) { - if (!_configs.product_exists(product)) { - // return more specific error for missing product. - COMPLAIN_PRODUCT; - return NO_PRODUCT; - } - COMPLAIN_APPLICATION; - return NO_APPLICATION; - } - - if (!_configs.add_startup_entry(product, app_name, parameters, one_shot)) { - // most likely problem is that it was already there. - return EXISTING; - } - - return OKAY; -} - -outcome launch_manager::remove_from_startup(const astring &product, - const astring &app_name) -{ - FUNCDEF("remove_from_startup"); - LOCK_CONFIG; // this whole function modifies the config file. - -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); -#endif - - // get the specific entry for the program they want. - int level; - astring entry_found = _configs.find_program(product, app_name, level); - if (!entry_found) { - if (!_configs.product_exists(product)) { - // return more specific error for missing product. - COMPLAIN_PRODUCT; - return NO_PRODUCT; - } - COMPLAIN_APPLICATION; - return NO_APPLICATION; - } -//hmmm: is product required for this for real? - - if (!_configs.remove_startup_entry(product, app_name)) { - // the entry couldn't be removed, probably doesn't exist. - return NO_APPLICATION; - } - - return OKAY; -} - -outcome launch_manager::query_application(const astring &product, - const astring &app_name) -{ - FUNCDEF("query_application"); -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); -#endif - - { - LOCK_CONFIG; - // get the specific entry for the program they want. - int level; - astring entry_found = _configs.find_program(product, app_name, level); - if (!entry_found) { - if (!_configs.product_exists(product)) { - // return more specific error for missing product. - COMPLAIN_PRODUCT; - return NO_PRODUCT; - } - COMPLAIN_APPLICATION; - return NO_APPLICATION; - } - } - - // now seek that program name in the current list of processes. - astring program_name(app_name); - program_name.to_lower(); - - int_set pids; - if (!find_process(app_name, pids)) - return NOT_RUNNING; - return OKAY; -} - -outcome launch_manager::start_graceful_close(const astring &product, - const astring &app_name) -{ - FUNCDEF("start_graceful_close"); -//hmmm: record this app as one we need to watch. - - { - // find that program name to make sure it's not already shutting down. - LOCK_ZOMBIES; - - for (int i = _going_down->length() - 1; i >= 0; i--) { - graceful_record &grace = (*_going_down)[i]; - if (grace._app_name.iequals(app_name)) { - return OKAY; - } - } - } - - if (!hoople_service::close_application(app_name)) - return NO_ANCHOR; - - int_set pids; - if (!find_process(app_name, pids)) { - LOG(astring("Failed to find process id for [") + app_name - + astring("], assuming it has already exited.")); - return OKAY; - } - - { - // add all the process ids, just in case there were multiple instances - // of the application somehow. - LOCK_ZOMBIES; - for (int i = 0; i < pids.elements(); i++) { - graceful_record to_add(pids[i], product, app_name); - *_going_down += to_add; - } - } - - return OKAY; -} - -bool launch_manager::get_processes(process_entry_array &processes) -{ - FUNCDEF("get_processes"); - if (!_procs->query_processes(processes)) { - LOG("failed to query processes!"); - return false; - } - return true; -} - -bool launch_manager::find_process(const astring &app_name_in, int_set &pids) -{ -// FUNCDEF("find_process"); - pids.clear(); - process_entry_array processes; - if (!get_processes(processes)) return false; - return process_control::find_process_in_list(processes, app_name_in, pids); -} - -outcome launch_manager::zap_process(const astring &product, - const astring &app_name_key, bool graceful) -{ - FUNCDEF("zap_process"); - -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("product \"") + product + "\", application \"" + app_name_key - + (graceful? "\", Graceful Close" : "\", Brutal Close")); -#endif - - if (_tracking_exclusions->member(app_name_key)) { - // the non-tracked applications are never reported as running since they're - // not allowed to be zapped anyhow. - return NOT_RUNNING; - } - - // use the real program name from here onward. - astring app_name; - { - LOCK_CONFIG; - - // get the specific entry for the program they want. - int level; - app_name = _configs.find_program(product, app_name_key, level); - if (!app_name) { - if (!_configs.product_exists(product)) { - // return more specific error for missing product. - COMPLAIN_PRODUCT; - return NO_PRODUCT; - } - COMPLAIN_APPLICATION; - return NO_APPLICATION; - } - // truncate the directory and just use the base. - app_name = filename(app_name).basename(); - } - - // if they want a graceful close, start by trying that. if it appears to - // have succeeded, then we exit and let the thread take care of ensuring - // the app goes down. - outcome to_return = NOT_RUNNING; - if (graceful) - to_return = start_graceful_close(product, app_name); - if (to_return == OKAY) - return OKAY; // maybe finished the close. - - // they want a harsh close of this application or the graceful close failed. - astring program_name(app_name); - int_set pids; - if (!find_process(program_name, pids)) { -#ifdef DEBUG_PROCESS_MANAGER - LOG(program_name + " process was not running.") -#endif - return NOT_RUNNING; - } - - // search for the application in the process list. - bool failed = false; - for (int i = 0; i < pids.elements(); i++) { - bool ret = _procs->zap_process(pids[i]); - if (ret) { - LOG(astring(astring::SPRINTF, "Killed process %d [", - pids[i]) + program_name + astring("]")); - } else { - LOG(astring(astring::SPRINTF, "Failed to zap process %d [", - pids[i]) + program_name + astring("]")); - } - if (!ret) failed = true; - } -// kind of a bizarre return, but whatever. it really should be some -// failure result since the zap failed. - return failed? ACCESS_DENIED : OKAY; -} - -outcome launch_manager::shut_down_launching_services(const astring &secret_word) -{ - FUNCDEF("shut_down_launching_services"); - LOG("checking secret word..."); -//hmmm: changing the secret word here. - if (secret_word.equal_to("MarblesAreRoundishYo")) { - LOG("it's correct; ending launch capabilities."); - } else { - LOG("the secret word is wrong. continuing normal operation."); - return BAD_PROGRAM; - } - _stop_launching = true; - return OKAY; -} - -outcome launch_manager::reenable_launching_services(const astring &secret_word) -{ - FUNCDEF("reenable_launching_services"); - LOG("checking secret word..."); - if (secret_word.equal_to("MarblesAreRoundishYo")) { - LOG("it's correct; resuming launch capabilities."); - } else { - LOG("the secret word is wrong. continuing with prior mode."); - return BAD_PROGRAM; - } - _stop_launching = false; - return OKAY; -} - -#define GET_PROCESSES \ - if (!retrieved_processes) { \ - retrieved_processes = true; \ - if (!get_processes(processes)) { \ - LOG("failed to retrieve process list from OS!"); \ - return; /* badness. */ \ - } \ - } - -void launch_manager::push_timed_activities(process_entry_array &processes) -{ - FUNCDEF("push_timed_activities"); - - // make sure we started the applications that were slated for execution at - // system startup time. we wait on this until the first thread activation - // to give other processes some breathing room right at startup time. - // also, it then doesn't block the main service thread. - if (!_started_initial_apps && (*_startup_time <= time_stamp()) ) { - // launch all the apps that are listed for system startup. - LOG("starting up the tasks registered for system initiation."); - launch_startup_apps(); - _started_initial_apps = true; - } - - if (!_started_initial_apps) { - _checker->reschedule(200); - // keep hitting this function until we know we can relax since the - // startup apps have been sent off. - } - - bool retrieved_processes = false; - - { - // loop over the death records we've got and check on the soon to be gone. - LOCK_ZOMBIES; - - for (int i = _going_down->length() - 1; i >= 0; i--) { - graceful_record &grace = (*_going_down)[i]; - - GET_PROCESSES; // load them if they hadn't been. - - int_set pids; - if (!process_control::find_process_in_list(processes, grace._app_name, - pids)) { - // the app can't be found as running, so whack the record for it. -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("cannot find app ") + grace._app_name - + " as running still; removing its dying record"); -#endif - _going_down->zap(i, i); - continue; - } - if (!pids.member(grace._pid)) { - // that particular instance exited on its own, so whack the record. -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("app ") + grace._app_name - + " exited on its own; removing its dying record"); -#endif - _going_down->zap(i, i); - continue; - } - - if (grace._started <= time_stamp(-GRACEFUL_SLACK)) { - // time to force it. - LOG(astring("Application ") + grace._app_name + " is unresponsive to " - "the graceful shutdown request. Now zapping it instead."); - if (!_procs->zap_process(grace._pid)) - LOG("Devolved graceful shutdown failed as zap_process also."); - _going_down->zap(i, i); - continue; - } - - // it's not time to dump this one yet, so keep looking at others. - } - } - - { - // now loop over the list of our active kids and make sure that they are - // all still running. if they aren't, then toss the record out. - LOCK_KIDS; - - for (int i = _our_kids->length() - 1; i >= 0; i--) { - graceful_record &grace = (*_our_kids)[i]; - - GET_PROCESSES; // load them if they hadn't been. - - int_set pids; - if (!process_control::find_process_in_list(processes, grace._app_name, - pids)) { - // the app can't be found as running, so whack the record for it. -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("cannot find kid ") + grace._app_name - + " as still running; removing its normal record"); -#endif - _our_kids->zap(i, i); - continue; - } - if (!pids.member(grace._pid)) { - // that particular instance exited on its own, so whack the record. -#ifdef DEBUG_PROCESS_MANAGER - LOG(astring("kid ") + grace._app_name - + " exited on its own; removing its normal record"); -#endif - _our_kids->zap(i, i); - continue; - } - - // this kid is still going, so keep its record. - } - } -} - -} //namespace. - diff --git a/core/library/application/launch_manager.h b/core/library/application/launch_manager.h deleted file mode 100644 index 33e4aef8..00000000 --- a/core/library/application/launch_manager.h +++ /dev/null @@ -1,186 +0,0 @@ - -// current bad issues: -// -// this class does not really provide a notion of the process name AND process id as being -// a pair one can operate on. mostly it assumes a bunch of singletons or that it's okay -// to whack all of them. -// that could be fixed by making the procname+procid pair into a unit in all functions, -// and make kill_all be a specialization of that. - - -#ifndef LAUNCH_MANAGER_CLASS -#define LAUNCH_MANAGER_CLASS - -/*****************************************************************************\ -* * -* Name : launch_manager -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace application { - -class graceful_array; -class launch_manager_thread; - -//! Provides methods for starting, stopping and checking on processes. -/*! - This includes support for graceful shutdowns and background handling of - exiting processes. -*/ - -class launch_manager : public virtual basis::root_object -{ -public: - launch_manager(processes::configured_applications &config); - //!< the launch_manager needs a configuration set to work with. - - virtual ~launch_manager(); - - DEFINE_CLASS_NAME("launch_manager"); - - enum outcomes { - OKAY = basis::common::OKAY, - EXISTING = basis::common::EXISTING, - //!< the entry already exists and overwriting is disallowed. - ACCESS_DENIED = basis::common::ACCESS_DENIED, - //!< the requested operation was not permitted. - - DEFINE_API_OUTCOME(FILE_NOT_FOUND, -53, "The file specified for the " - "application doesn't exist, as far as we can tell"), - DEFINE_API_OUTCOME(NO_PRODUCT, -54, "The product specified does not exist"), - DEFINE_API_OUTCOME(NO_APPLICATION, -55, "The application is not listed for " - "the product"), - DEFINE_API_OUTCOME(NOT_RUNNING, -56, "The program is not currently active, " - "according to the OS process list"), - DEFINE_API_OUTCOME(BAD_PROGRAM, -57, "The file existed but doesn't appear " - "to be a valid program image"), - DEFINE_API_OUTCOME(NO_ANCHOR, -58, "This occurs when the graceful shutdown " - "process cannot find the special anchor window that implements the " - "client side of a graceful shutdown"), - DEFINE_API_OUTCOME(LAUNCH_FAILED, -59, "The program existed and seemed " - "valid but its launch failed for some reason"), - DEFINE_API_OUTCOME(FROZEN, -60, "The application is broken somehow; the " - "system reports it as non-responsive"), - // note that these values are reversed, since the ordering is negative. - FIRST_OUTCOME = FROZEN, // hold onto start of range. - LAST_OUTCOME = FILE_NOT_FOUND // hold onto end of range. - }; - - static const char *outcome_name(const basis::outcome &to_name); - //!< returns the text associated with "to_name". - - basis::outcome launch_now(const basis::astring &product, const basis::astring &app_name, - const basis::astring ¶meters); - //!< starts the application "app_name" now. - /*!< causes the program with "app_name" that's listed for the "product" to - be started with the "parameters". this can fail if the application - isn't listed or if the program can't be found or if the process can't - be created. */ - - basis::outcome launch_at_startup(const basis::astring &product, const basis::astring &app_name, - const basis::astring ¶meters, int one_shot); - //!< records an entry for the "app_name" to be launched at startup. - /*!< this does not launch the application now. if "one_shot" is true, the - application will only be started once and then removed from the list. */ - - basis::outcome remove_from_startup(const basis::astring &product, const basis::astring &app_name); - //!< takes the "app_name" out of the startup list. - - basis::outcome query_application(const basis::astring &product, const basis::astring &app_name); - //!< retrieves the current state of the program with "app_name". - - basis::outcome zap_process(const basis::astring &product, const basis::astring &app_name, - bool graceful); - //!< zaps the process named "app_name". - /*!< if "graceful" is true, then the clean shutdown process is attempted. - otherwise the application is harshly terminated. */ - - void add_gag_exclusion(const basis::astring &exclusion); - //!< add an application that isn't subject to gagging. - /*!< if the launch_manager is gagged, the excluded applications can - still be started. */ - void add_tracking_exclusion(const basis::astring &exclusion); - //!< apps that aren't tracked when running. - /*!< if a zap is attempted on one of these applications, then - the process is not shut down. */ - - basis::outcome shut_down_launching_services(const basis::astring &secret_word); - //!< closes down the ability of clients to launch applications. - /*!< the checking and shut down functions continue to operate. this is - intended for use prior to a restart of the application controller, in - order to ensure that no remote clients can start new servers on this - machine. */ - - basis::outcome reenable_launching_services(const basis::astring &secret_word); - //!< undoes the gagging that the above "shut_down" function does. - /*!< this allows the launch_manager to continue operating normally. */ - - bool services_disabled() const { return _stop_launching; } - //!< returns true if the capability to launch new processes is revoked. - - void push_timed_activities(processes::process_entry_array &processes); - //!< keeps any periodic activities going. - /*!< this includes such tasks as zapping processes that have gone beyond - their time limit for graceful shutdown. */ - - void stop_everything(); - //!< closes down the operation of this object. - -private: - processes::configured_applications &_configs; //!< manages the entries for companies. - bool _started_initial_apps; //!< true if we launched the boot apps. - launch_manager_thread *_checker; //!< keeps periodic activities going. - basis::mutex *_config_lock; //!< the synchronizer for our configuration entries. - graceful_array *_going_down; //!< record of graceful shutdowns in progress. - basis::mutex *_zombie_lock; //!< the synchronizer for the dying processes. - graceful_array *_our_kids; //!< the processes we've started. - basis::mutex *_scamp_lock; //!< the synchronizer for the list of children. - bool _stop_launching; //!< true if no launches should be allowed any more. - timely::time_stamp *_startup_time; - //!< the time we feel it's safe to launch the startup apps. - /*!< we delay this some so that the launch_manager doesn't immediately - soak up too much CPU. */ - processes::process_control *_procs; //!< gives us access to the process list. - structures::string_set *_gag_exclusions; //!< apps that aren't subject to gag law. - structures::string_set *_tracking_exclusions; //!< apps that aren't tracked when running. - - bool get_processes(processes::process_entry_array &processes); - //!< grabs the list of "processes" or returns false. - - bool find_process(const basis::astring &app_name, structures::int_set &pids); - //!< locates any instances of "app_name" and returns process ids in "pids". - - basis::outcome start_graceful_close(const basis::astring &product, const basis::astring &app_name); - //!< attempts to close the "app_name". - /*!< if the application doesn't shut down within the time limit, it is - eventually zapped harshly. */ - - void launch_startup_apps(); - //!< iterates over the list of startup apps and creates a process for each. - - void stop_all_kids(); - //!< closes all dependent processes when its time to stop the service. -}; - -} //namespace. - -#endif - diff --git a/core/library/application/makefile b/core/library/application/makefile deleted file mode 100644 index 758240c5..00000000 --- a/core/library/application/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = application -TYPE = library -SOURCE = application_shell.cpp callstack_tracker.cpp command_line.cpp dll_root.cpp \ - hoople_service.cpp launch_manager.cpp memory_checker.cpp redirecter.cpp shared_memory.cpp \ - singleton_application.cpp windoze_helper.cpp -TARGETS = application.lib -LOCAL_LIBS_USED = basis - -include cpp/rules.def - diff --git a/core/library/application/memory_checker.cpp b/core/library/application/memory_checker.cpp deleted file mode 100644 index 2426c785..00000000 --- a/core/library/application/memory_checker.cpp +++ /dev/null @@ -1,414 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : memory_checker * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// note: parts of this have been around since at least 1998, but this code was -// newly revised for memory checking in february of 2007. --cak - -#ifdef ENABLE_MEMORY_HOOK - -#include "definitions.h" -#include "log_base.h" -#include "memory_checker.h" -#include "mutex.h" -#include "utility.h" - -#include -#include -#include - -const int MAXIMUM_HASH_SLOTS = 256 * KILOBYTE; - // that's a whole lot of slots. this number is basically multiplied by - // the sizeof(memory_bin) to get full memory footprint. - -const int SINGLE_LINE_SIZE_ESTIMATE = 200; - // we are guessing that the average line of memory printout will take - // this many characters. that includes the size, the pointer value, - // and the location and line number. - -const int RESERVED_AREA = 1000; - // we reserve this much space in the string generated for the memory bin - // dumps. it will be used for adding more information to the string. - -#define CLEAR_ALLOCATED_MEMORY - // uncomment this to ensure that new memory gets its contents set to zero. - // neither new nor malloc does this, but it can help finding bugs from - // people re-using deallocated memory. - -#define MEMORY_CHECKER_STATISTICS - // uncomment to enable code that analyzes how many allocations were new and - // so forth. this will make the object run a bit slower. - -//#define DEBUG_MEMORY_CHECKER - // uncomment for super noisy version. - -////////////// - -// define the replacement new and delete operators. - -#include -void *operator new(size_t size, char *file, int line) throw (std::bad_alloc) -{ return program_wide_memories().provide_memory(size, file, line); } -#include - -void operator delete(void *ptr) throw () -{ program_wide_memories().release_memory(ptr); } - -////////////// - -// memlink is one link in a chain of memories. it's singly linked, so our -// algorithms have to explicitly remember the parent. - -class memlink -{ -public: - void *_chunk; //!< the chunk of memory held (not real address). - /*!< NOTE: we store the chunk as it looks to the outside world, rather - than using its real address. this eliminates a bit of ambiguity in the - code. */ - memlink *_next; //!< the next memory wrapper in the list. - int _size; //!< the size of the chunk delivered. - char *_where; //!< the name of the place that created it. - int _line; //!< the line number where the item was allocated. -#ifdef ENABLE_CALLSTACK_TRACKING - char *_stack; //!< records the stack seen at time of allocation. -#endif - - void construct(void *ptr, int size, char *where, int line) { - _next = NIL; - _chunk = ptr; - _size = size; - _where = strdup(where); // uses malloc, not new, so we're safe. - if (strlen(_where) > SINGLE_LINE_SIZE_ESTIMATE - 40) { - // if we will not have room for the full line, we crop it. - _where[SINGLE_LINE_SIZE_ESTIMATE - 40] = '\0'; - } - _line = line; -#ifdef ENABLE_CALLSTACK_TRACKING - _stack = program_wide_stack_trace().full_trace(); -///printf("stack here:\n%s", _stack); -#endif - } - - void destruct() { - free(_chunk); _chunk = NIL; - free(_where); _where = NIL; - _next = NIL; - _size = 0; - _line = 0; -#ifdef ENABLE_CALLSTACK_TRACKING - free(_stack); _stack = NIL; -#endif - } -}; - -////////////// - -//pretty lame here so far. -#ifdef MEMORY_CHECKER_STATISTICS - // simple stats: the methods below will tweak these numbers if memory_checker - // statistics are enabled. ints won't do here, due to the number of - // operations in a long-running program easily overflowing that size. - - // this bank of statistics counts the number of times memory was treated - // a certain way. - double _stat_new_allocations = 0; // this many new allocations occurred. - double _stat_freed_allocations = 0; // number of freed blocks. - // next bank of stats are the sizes of the memory that were stowed, etc. - double _stat_new_allocations_size = 0; // this many bytes got allocated. - double _stat_freed_allocations_size = 0; // this many bytes were freed. -#endif - -////////////// - -//! the memory bin holds a list of chunks of memory in memlink objects. - -class memory_bin -{ -public: - void construct() { - _head = NIL; - _count = 0; - _lock = (mutex_base *)malloc(sizeof(mutex_base)); - _lock->construct(); - } - void destruct() { - _lock->destruct(); - free(_lock); - } - - int count() const { return _count; } - - int record_memory(void *ptr, int size, char *where, int line) { - memlink *new_guy = (memlink *)malloc(sizeof(memlink)); - new_guy->construct(ptr, size, where, line); - _lock->lock(); - // this code has the effect of putting more recent allocations first. - // if they happen to get cleaned up right away, that's nice and fast. - new_guy->_next = _head; - _head = new_guy; - _count++; - _lock->unlock(); - return common::OKAY; // seems to have worked fine. - } - - int release_memory(void *to_release) { - _lock->lock(); - // search the bin to locate the item specified. - memlink *current = _head; // current will scoot through the list. - memlink *previous = NIL; // previous remembers the parent node, if any. - while (current) { - if (current->_chunk == to_release) { -#ifdef MEMORY_CHECKER_STATISTICS - // record that it went away. - _stat_freed_allocations += 1.0; - _stat_freed_allocations_size += current->_size; -#endif -#ifdef DEBUG_MEMORY_CHECKER - printf("found %p listed, removing for %s[%d]\n", to_release, - current->_where, current->_line); -#endif - // unlink this one and clean up; they don't want it now. - if (!previous) { - // this is the head we're modifying. - _head = current->_next; - } else { - // not the head, so there was a valid previous element. - previous->_next = current->_next; - } - // now trash that goner's house. - current->destruct(); - free(current); - _count--; - _lock->unlock(); - return common::OKAY; - } - // the current node isn't it; jump to next node. - previous = current; - current = current->_next; - } -#ifdef DEBUG_MEMORY_CHECKER - printf("failed to find %p listed.\n", to_release); -#endif - _lock->unlock(); - return common::NOT_FOUND; - } - - void dump_list(char *add_to, int &curr_size, int max_size) { - int size_alloc = 2 * SINGLE_LINE_SIZE_ESTIMATE; // room for one line. - char *temp_str = (char *)malloc(size_alloc); - memlink *current = _head; // current will scoot through the list. - while (current) { - temp_str[0] = '\0'; - sprintf(temp_str, "\n\"%s[%d]\", \"size %d\", \"addr %p\"\n", - current->_where, current->_line, current->_size, current->_chunk); - int len_add = strlen(temp_str); - if (curr_size + len_add < max_size) { - strcat(add_to, temp_str); - curr_size += len_add; - } -#ifdef ENABLE_CALLSTACK_TRACKING - len_add = strlen(current->_stack); - if (curr_size + len_add < max_size) { - strcat(add_to, current->_stack); - curr_size += len_add; - } -#endif - current = current->_next; - } - free(temp_str); - } - -private: - memlink *_head; // our first, if any, item. - mutex_base *_lock; // protects our bin from concurrent access. - int _count; // current count of items held. -}; - -////////////// - -class allocation_memories -{ -public: - void construct(int num_slots) { - _num_slots = num_slots; - _bins = (memory_bin *)malloc(num_slots * sizeof(memory_bin)); - for (int i = 0; i < num_slots; i++) - _bins[i].construct(); - } - - void destruct() { - // destroy each bin in our list. - for (int i = 0; i < _num_slots; i++) { - _bins[i].destruct(); - } - free(_bins); - _bins = NIL; - } - - int compute_slot(void *ptr) { - return utility::hash_bytes(&ptr, sizeof(void *)) % _num_slots; - } - - void *provide_memory(int size_needed, char *file, int line) { - void *new_allocation = malloc(size_needed); - // slice and dice pointer to get appropriate hash bin. - int slot = compute_slot(new_allocation); -#ifdef DEBUG_MEMORY_CHECKER - printf("using slot %d for %p\n", slot, new_allocation); -#endif - _bins[slot].record_memory(new_allocation, size_needed, file, line); -#ifdef MEMORY_CHECKER_STATISTICS - _stat_new_allocations += 1.0; - _stat_new_allocations_size += size_needed; -#endif - return new_allocation; - } - - int release_memory(void *to_drop) { - int slot = compute_slot(to_drop); // slice and dice to get bin number. -#ifdef DEBUG_MEMORY_CHECKER - printf("removing mem %p from slot %d.\n", to_drop, slot); -#endif - return _bins[slot].release_memory(to_drop); - } - - //! this returns a newly created string with all current contents listed. - /*! this returns a simple char * pointer that was created with malloc. - the invoker *must* free the returned pointer. we use malloc/free here to - avoid creating a wacky report that contains a lot of new allocations for - the report itself. that seems like it could become a problem. */ - char *report_allocations() { - // count how many allocations we have overall. - int full_count = 0; - for (int i = 0; i < _num_slots; i++) { -///if (_bins[i].count()) printf("%d adding count %d\n", i, _bins[i].count()); - full_count += _bins[i].count(); - } -///printf("full count is %d\n", full_count); - // calculate a guess for how much space we need to show all of those. - int alloc_size = full_count * SINGLE_LINE_SIZE_ESTIMATE + RESERVED_AREA; - char *to_return = (char *)malloc(alloc_size); - to_return[0] = '\0'; - if (full_count) { - strcat(to_return, "===================\n"); - strcat(to_return, "Unfreed Allocations\n"); - strcat(to_return, "===================\n"); - } - int curr_size = strlen(to_return); // how much in use so far. - for (int i = 0; i < _num_slots; i++) { - _bins[i].dump_list(to_return, curr_size, alloc_size - RESERVED_AREA); - } - return to_return; - } - - // this is fairly resource intensive, so don't dump the state out that often. - char *text_form(bool show_outstanding) { - char *to_return = NIL; - if (show_outstanding) { - to_return = report_allocations(); - } else { - to_return = (char *)malloc(RESERVED_AREA); - to_return[0] = '\0'; - } -#ifdef MEMORY_CHECKER_STATISTICS - char *temp_str = (char *)malloc(4 * SINGLE_LINE_SIZE_ESTIMATE); - - sprintf(temp_str, "=================\n"); - strcat(to_return, temp_str); - sprintf(temp_str, "Memory Statistics\n"); - strcat(to_return, temp_str); - sprintf(temp_str, "=================\n"); - strcat(to_return, temp_str); - sprintf(temp_str, "Measurements taken across entire program runtime:\n"); - strcat(to_return, temp_str); - sprintf(temp_str, " %.0f new allocations.\n", _stat_new_allocations); - strcat(to_return, temp_str); - sprintf(temp_str, " %.4f new Mbytes.\n", - _stat_new_allocations_size / MEGABYTE); - strcat(to_return, temp_str); - sprintf(temp_str, " %.0f freed deallocations.\n", - _stat_freed_allocations); - strcat(to_return, temp_str); - sprintf(temp_str, " %.4f freed Mbytes.\n", - _stat_freed_allocations_size / MEGABYTE); - strcat(to_return, temp_str); - - free(temp_str); -#endif - return to_return; - } - -private: - memory_bin *_bins; //!< each bin manages a list of pointers, found by hash. - int _num_slots; //!< the number of hash slots we have. -}; - -////////////// - -void memory_checker::construct() -{ - _mems = (allocation_memories *)malloc(sizeof(allocation_memories)); - _mems->construct(MAXIMUM_HASH_SLOTS); - _unusable = false; - _enabled = true; -} - -void memory_checker::destruct() -{ - if (_unusable) return; // already gone. -if (!_mems) printf("memory_checker::destruct being invoked twice!\n"); - - // show some stats about memory allocation. - char *mem_state = text_form(true); - printf("%s", mem_state); -///uhhh free(mem_state); -//the above free seems to totally die if we allow it to happen. - - _unusable = true; - - _mems->destruct(); - free(_mems); - _mems = NIL; -} - -void *memory_checker::provide_memory(size_t size, char *file, int line) -{ - if (_unusable || !_enabled) return malloc(size); - return _mems->provide_memory(size, file, line); -} - -int memory_checker::release_memory(void *ptr) -{ - if (_unusable || !_enabled) { - free(ptr); - return common::OKAY; - } - return _mems->release_memory(ptr); -} - -char *memory_checker::text_form(bool show_outstanding) -{ - if (_unusable) return strdup("already destroyed memory_checker!\n"); - return _mems->text_form(show_outstanding); -} - -////////////// - -#endif // enable memory hook - - - diff --git a/core/library/application/memory_checker.h b/core/library/application/memory_checker.h deleted file mode 100644 index d899abd4..00000000 --- a/core/library/application/memory_checker.h +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef MEMORY_CHECKER_CLASS -#define MEMORY_CHECKER_CLASS - -/*****************************************************************************\ -* * -* Name : memory_checker * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "definitions.h" - -#ifdef ENABLE_MEMORY_HOOK - -#include "build_configuration.h" -uhh - -// forward. -class allocation_memories; -class memory_checker; - -////////////// - -memory_checker BASIS_EXTERN &program_wide_memories(); - //!< a global version of the memory checker to access memory tracking. - /*!< this accesses the singleton object that tracks all memory allocations - occuring in the program via calls to new and delete. it should be used - rather than creating a memory_checker anywhere else. */ - -////////////// - -//! Debugging assistance tool that checks for memory leaks. -/*! - Provides allocation checking for heap memory for C++. This is used as a - replacement for the standard new and delete operations. No object should - really need to deal with this class directly; it is hooked in automatically - unless the ENABLE_MEMORY_HOOK macro is defined. Generally, release builds - should not have this enabled, since it will slow things down tremendously. - NOTE: this object can absolutely not be hooked into callstack tracker, since - this object implements all c++ memory, including the tracker's. This object - will use the backtrace information if it's available. -*/ - -class memory_checker -{ -public: - void construct(); //!< faux constructor for mallocing. - - void destruct(); //!< faux destructor shuts down object. - - //! turn off memory checking. - void disable() { _enabled = false; } - //! turn memory checking back on. - void enable() { _enabled = true; } - //! reports on whether the memory checker is currently in service or not. - bool enabled() const { return _enabled; } - - void *provide_memory(size_t size, char *file, int line); - //!< returns a chunk of memory with the "size" specified. - /*!< this is the replacement method for the new operator. we will be - calling this instead of the compiler provided new. the "file" string - should be a record of the location where this is invoked, such as is - provided by the __FILE__ macro. the "line" should be set to the line - number within the "file", if applicable (use __LINE__). */ - - int release_memory(void *ptr); - //!< drops our record for the memory at "ptr". - /*!< this is the only way to remove an entry from our listings so that - it will not be reported as a leak. we do not currently gather any info - about where the release is invoked. if there was a problem, the returned - outcome will not be OKAY. */ - - char *text_form(bool show_outstanding); - //!< returns a newly allocated string with the stats for this object. - /*!< if "show_outstanding" is true, then all outstanding allocations are - displayed in the string also. the invoker *must* free the returned - pointer. */ - -private: - allocation_memories *_mems; //!< internal object tracks all allocations. - bool _unusable; //!< true after destruct is called. - bool _enabled; //!< true if the object is okay to use. -}; - -#else // enable memory hook. - // this section disables the memory checker entirely. - #define program_wide_memories() - // define a do nothing macro for the global memory tracker. -#endif // enable memory hook. - -#endif // outer guard. - diff --git a/core/library/application/redirecter.cpp b/core/library/application/redirecter.cpp deleted file mode 100644 index 78091446..00000000 --- a/core/library/application/redirecter.cpp +++ /dev/null @@ -1,506 +0,0 @@ -/*****************************************************************************\ -* * -* Name : stdio_redirecter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "redirecter.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef __UNIX__ - #include - #include -#endif - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace processes; -using namespace textual; - -namespace application { - -const int IO_PAUSE_PERIOD = 50; // sleep for this long between read attempts. - -const int BUFFER_SIZE = 4096; // maximum we will read at once. - -#undef LOG -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) - -const char *REDIRECTER_INI = "redirecter.ini"; - // used to report process ids, since there are users that need this - // info currently. - -const char *PROCESS_SECTION = "process_id"; - // the section in the ini file where we store our process ids. -//hmmm: above should be removed and pushed into stdio wrapper. - -////////////// - -class reader_thread : public ethread -{ -public: - reader_thread(stdio_redirecter &parent, bool is_stdout) - : ethread(), _is_stdout(is_stdout), _parent(parent) { - } - - virtual ~reader_thread() { - } - - virtual void perform_activity(void *formal(ptr)) { - while (!should_stop()) { - _parent.std_thread_action(_is_stdout); - } - } - -private: - bool _is_stdout; // if true, then stdout, if false, then stderr. - stdio_redirecter &_parent; -}; - -////////////// - -stdio_redirecter::stdio_redirecter() -: -#ifdef __WIN32__ - _child_in(NIL), _child_out(NIL), _child_err(NIL), - _parent_in(NIL), _parent_out(NIL), _parent_err(NIL), - _app_handle(NIL), -#endif - _command(new astring), - _parms(new astring), - _persistent_result(OKAY), - _stdout_reader(new reader_thread(*this, true)), - _stderr_reader(new reader_thread(*this, false)), - _stdout_queue(new byte_array), - _stderr_queue(new byte_array), - _lock(new mutex), - _process_id(0), - _exit_value(0) -{ -} - -stdio_redirecter::stdio_redirecter(const astring &command, - const astring ¶meters) -: -#ifdef __WIN32__ - _child_in(NIL), _child_out(NIL), _child_err(NIL), - _parent_in(NIL), _parent_out(NIL), _parent_err(NIL), - _app_handle(NIL), -#endif - _command(new astring(command)), - _parms(new astring(parameters)), - _persistent_result(OKAY), - _stdout_reader(new reader_thread(*this, true)), - _stderr_reader(new reader_thread(*this, false)), - _stdout_queue(new byte_array), - _stderr_queue(new byte_array), - _lock(new mutex), - _process_id(0), - _exit_value(0) -{ - outcome ret = create_pipes(); - if (ret != OKAY) { _persistent_result = ret; return; } - ret = launch_program(_process_id); - if (ret != OKAY) { _persistent_result = ret; return; } -} - -stdio_redirecter::~stdio_redirecter() -{ - zap_program(); - _process_id = 0; - - WHACK(_stdout_queue); - WHACK(_stderr_queue); - WHACK(_stdout_reader); - WHACK(_stderr_reader); - WHACK(_command); - WHACK(_parms); - WHACK(_lock); -} - -outcome stdio_redirecter::reset(const astring &command, - const astring ¶meters) -{ - zap_program(); - _process_id = 0; - - *_command = command; - *_parms = parameters; - _persistent_result = OKAY; - - outcome ret = create_pipes(); - if (ret != OKAY) { _persistent_result = ret; return ret; } - ret = launch_program(_process_id); - if (ret != OKAY) { _persistent_result = ret; return ret; } - return ret; -} - -outcome stdio_redirecter::create_pipes() -{ - FUNCDEF("create_pipes"); -#ifdef __UNIX__ - // the input and output here are from the perspective of the parent - // process and not the launched program. - if (pipe(_input_fds)) { - LOG("failure to open an unnamed pipe for input."); - return ACCESS_DENIED; - } - if (pipe(_output_fds)) { - LOG("failure to open an unnamed pipe for output."); - return ACCESS_DENIED; - } - if (pipe(_stderr_fds)) { - LOG("failure to open an unnamed pipe for stderr."); - return ACCESS_DENIED; - } -#elif defined (__WIN32__) - // set up the security attributes structure that governs how the child - // process is created. - SECURITY_ATTRIBUTES sa; - ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); - sa.nLength= sizeof(SECURITY_ATTRIBUTES); - sa.lpSecurityDescriptor = NIL; - sa.bInheritHandle = true; - - HANDLE in_temp = NIL, out_temp = NIL, err_temp = NIL; - - // create pipes that we will hook up to the child process. these are - // currently inheritable based on the security attributes. - if (!CreatePipe(&_child_in, &in_temp, &sa, 0)) return ACCESS_DENIED; - if (!CreatePipe(&out_temp, &_child_out, &sa, 0)) return ACCESS_DENIED; - if (!CreatePipe(&err_temp, &_child_err, &sa, 0)) return ACCESS_DENIED; - - HANDLE process_handle = GetCurrentProcess(); - // retrieve process handle for use in system calls below. since it's - // a pseudo handle, we don't need to close it. - - // create new handles for the parent process (connected to this object) to - // use. the false indicates that the child should not inherit the properties - // on these because otherwise it cannot close them. - if (!DuplicateHandle(process_handle, in_temp, process_handle, &_parent_in, - 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; - if (!DuplicateHandle(process_handle, out_temp, process_handle, &_parent_out, - 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; - if (!DuplicateHandle(process_handle, err_temp, process_handle, &_parent_err, - 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; - - // close out the handles that we're done with and don't want the child to - // inherit. - CloseHandle(in_temp); - CloseHandle(out_temp); - CloseHandle(err_temp); -#endif - - return OKAY; -} - -outcome stdio_redirecter::launch_program(int &new_process_id) -{ -// FUNCDEF("launch_program"); - new_process_id = 0; -#ifdef __UNIX__ - int fork_ret = fork(); - if (fork_ret == 0) { - // this is the child. - close(_output_fds[1]); // close our *input* pipe's output fd. - dup2(_output_fds[0], 0); // close our stdin and replace with input pipe. - close(_input_fds[0]); // close our *output* pipe's input fd. - dup2(_input_fds[1], 1); // close our stdout and replace with output pipe. - close(_stderr_fds[0]); // close stderr input fd. - dup2(_stderr_fds[1], 2); // close our stderr and pipe it to parent. - // now we want to launch the program for real. - processes::char_star_array parms = launch_process::break_line(*_command, *_parms); - execv(_command->s(), parms.observe()); - // oops. failed to exec if we got to here. - exit(1); - } else { - // this is the parent. - _process_id = fork_ret; // save the child's process id. - new_process_id = _process_id; // set the returned id. - close(_output_fds[0]); // close our *output* pipe's input fd. - close(_input_fds[1]); // close our *input* pipe's output fd. - close(_stderr_fds[1]); // close the child's stderr output side. - // now we should have a set of pipes that talk to the child. - } -#elif defined (__WIN32__) - // set up the startup info struct. - STARTUPINFO si; - ZeroMemory(&si, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.hStdInput = _child_in; - si.hStdOutput = _child_out; - si.hStdError = _child_err; - si.wShowWindow = SW_HIDE; // we'll hide the console window. - - // setup the security attributes for the new process. - SECURITY_DESCRIPTOR *sec_desc = (SECURITY_DESCRIPTOR *)GlobalAlloc - (GPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); - InitializeSecurityDescriptor(sec_desc, SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl(sec_desc, -1, 0, 0); - LPSECURITY_ATTRIBUTES sec_attr = (LPSECURITY_ATTRIBUTES)GlobalAlloc(GPTR, - sizeof(SECURITY_ATTRIBUTES)); - sec_attr->nLength = sizeof(SECURITY_ATTRIBUTES); - sec_attr->lpSecurityDescriptor = sec_desc; - sec_attr->bInheritHandle = true; - - astring cmd = *_command; - if (cmd[0] != '"') - cmd.insert(0, "\""); - if (cmd[cmd.end()] != '"') - cmd += "\""; - cmd += " "; - cmd += *_parms; - - // fork off the process. - PROCESS_INFORMATION pi; - BOOL success = CreateProcess(NIL, to_unicode_temp(cmd), sec_attr, NIL, - true, CREATE_NEW_CONSOLE, NIL, NIL, &si, &pi); - - // cleanup junk we allocated. - if (sec_attr != NIL) GlobalFree(sec_attr); - if (sec_desc != NIL) GlobalFree(sec_desc); - - if (success) { - // toss out the thread handle since we don't use it. - CloseHandle(pi.hThread); - // track the important handle, for our application. - _app_handle = pi.hProcess; -//hmmm: boot this stuff out into the stdio_wrapper class, which is the only -// thing that should do this. - ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY); - ini.store(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id()), - a_sprintf("%d", pi.dwProcessId)); - _process_id = pi.dwProcessId; - new_process_id = _process_id; - } else { - return NOT_FOUND; - } -#endif - - _stdout_reader->start(NIL); - _stderr_reader->start(NIL); - - return OKAY; -} - -bool stdio_redirecter::running() -{ - if (!_process_id) return false; // nothing to check. - if (_exit_value != 0) return false; // gone by now. -#ifdef __UNIX__ - int status; - pid_t pid = waitpid(_process_id, &status, WNOHANG); - if (!pid) return true; // still going. - if (!_exit_value) { -//hmmm: is that all we need from it? unprocessed exit value? - _exit_value = status; - } - if (WIFEXITED(status)) { - // the child exited on its own. - _process_id = 0; - return false; - } else if (WIFSIGNALED(status)) { - // the child was zapped by a signal. - _process_id = 0; - return false; - } - return true; -#elif defined (__WIN32__) - DWORD exit_value = 0; - // see if there's an exit code yet. if this fails with false, then the - // process is maybe long gone or something? - BOOL ret = GetExitCodeProcess(_app_handle, &exit_value); - if (ret) { - // store it if we had no previous version. - if (exit_value != STILL_ACTIVE) { - _exit_value = exit_value; - return false; - } - return true; - } else { - // this one seems to still be going. - return true; - } -#endif -} - -void stdio_redirecter::close_input() -{ -#ifdef __UNIX__ - close(_output_fds[1]); // shut down input to the child program. -#elif defined(__WIN32__) - if (_child_in) { CloseHandle(_child_in); _child_in = NIL; } - if (_parent_in) { CloseHandle(_parent_in); _parent_in = NIL; } -#endif -} - -void stdio_redirecter::zap_program() -{ - FUNCDEF("zap_program"); - _stdout_reader->cancel(); - _stderr_reader->cancel(); - -#ifdef __UNIX__ - close_input(); - close(_stderr_fds[0]); - close(_input_fds[0]); - - if (_process_id) { - kill(_process_id, 9); // end the program without any doubt. - } - _process_id = 0; -#elif defined(__WIN32__) - if (_app_handle) { - // none of the handle closing works if the app is still running. - // microsoft hasn't really got a clue, if you cannot close a file handle - // when you want to, but that's apparently what's happening. - TerminateProcess(_app_handle, 1); - } - - close_input(); - - if (_child_out) { CloseHandle(_child_out); _child_out = NIL; } - if (_parent_out) { CloseHandle(_parent_out); _parent_out = NIL; } - - if (_child_err) { CloseHandle(_child_err); _child_err = NIL; } - if (_parent_err) { CloseHandle(_parent_err); _parent_err = NIL; } - - // shut down the child process if it's still there. - if (_app_handle) { - DWORD ret; - -//hmmm: also should only be in the stdio wrapper program. -//hmmm: remove this in favor of the stdio wrapper or whomever tracking their -// own process id. - ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY); - ini.delete_entry(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id())); - - GetExitCodeProcess(_app_handle, &ret); - if (ret == STILL_ACTIVE) { - // it's still bumbling along; let's drop it. - TerminateProcess(_app_handle, 1); - if (WaitForSingleObject(_app_handle, 1000) == WAIT_TIMEOUT) { -///problem! - LOG("hmmm, we timed out waiting for the process to exit."); - } - } - CloseHandle(_app_handle); - _app_handle = NIL; - } -#endif - - _stdout_reader->stop(); - _stderr_reader->stop(); -} - -outcome stdio_redirecter::read(byte_array &received) -{ - received.reset(); - if (_persistent_result != OKAY) return _persistent_result; - auto_synchronizer l(*_lock); - if (!_stdout_queue->length()) return NONE_READY; -//hmmm: signal eof too! - received = *_stdout_queue; - _stdout_queue->reset(); - return common::OKAY; -} - -outcome stdio_redirecter::write(const astring &to_write, int &written) -{ - byte_array real_write(to_write.length(), (abyte *)to_write.observe()); - return write(real_write, written); -} - -outcome stdio_redirecter::write(const byte_array &to_write, int &written) -{ -// FUNCDEF("write"); - written = 0; - if (_persistent_result != OKAY) return _persistent_result; -#ifdef __UNIX__ - int writ = ::write(_output_fds[1], to_write.observe(), to_write.length()); - if (writ < 0) return ACCESS_DENIED; - written = writ; - return OKAY; -#elif defined(__WIN32__) - DWORD writ = 0; - BOOL ret = WriteFile(_parent_in, to_write.observe(), to_write.length(), - &writ, NIL); - written = writ; - if (ret) return OKAY; - else return ACCESS_DENIED; -#endif -} - -outcome stdio_redirecter::read_stderr(byte_array &received) -{ - received.reset(); - if (_persistent_result != OKAY) return _persistent_result; - auto_synchronizer l(*_lock); - if (!_stderr_queue->length()) return NONE_READY; -//signal eof too! - received = *_stderr_queue; - _stderr_queue->reset(); - return common::OKAY; -} - -void stdio_redirecter::std_thread_action(bool is_stdout) -{ -// FUNCDEF("std_thread_action"); - byte_array buff(BUFFER_SIZE + 1); -#ifdef __UNIX__ - bool ret = false; - int fd = _input_fds[0]; - if (!is_stdout) fd = _stderr_fds[0]; - if (!fd) return; // nothing to read from. - int bytes_read = ::read(fd, buff.access(), BUFFER_SIZE); - if (!bytes_read) { -//indicates end of file; set flags! - } else if (bytes_read > 0) { - ret = true; // there's new data in our buffer. - } -#elif defined(__WIN32__) - HANDLE where = _parent_out; - if (!is_stdout) where = _parent_err; - if (!where) return; // nothing to read from. - // read some data from the file. the function will return when a write - // operation completes or we get that much data. - DWORD bytes_read = 0; - BOOL ret = ReadFile(where, buff.access(), BUFFER_SIZE, &bytes_read, NIL); -//hmmm: if (ret && !bytes_read) {///set eof!!! } -#endif - if (ret && bytes_read) { - auto_synchronizer l(*_lock); - byte_array *queue = _stdout_queue; - if (!is_stdout) queue = _stderr_queue; - *queue += buff.subarray(0, bytes_read - 1); - } -} - -} //namespace. - - diff --git a/core/library/application/redirecter.h b/core/library/application/redirecter.h deleted file mode 100644 index 3f52f263..00000000 --- a/core/library/application/redirecter.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef STDIO_REDIRECTER_CLASS -#define STDIO_REDIRECTER_CLASS - -/*****************************************************************************\ -* * -* Name : stdio_redirecter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace application { - -//! Redirects I/O for a newly launched application. - -class stdio_redirecter : public virtual basis::root_object -{ -public: - stdio_redirecter(const basis::astring &command, const basis::astring ¶meters); - //!< controls the I/O for a program started with "command" and "parameters". - /*!< this creates an io redirecter that will trap the inputs and outputs - of a program called "command" that is launched with the "parameters". - the program can be communicated with via the read and write methods - below. */ - - stdio_redirecter(); - //!< creates a blank redirecter which should be started up with reset(). - - ~stdio_redirecter(); - //!< shuts down the program that was launched, if it's still running. - - basis::outcome reset(const basis::astring &command, const basis::astring ¶meters); - //!< shuts down the active program and starts with new parameters. - - DEFINE_CLASS_NAME("stdio_redirecter"); - - enum outcomes { - OKAY = basis::common::OKAY, - NOT_FOUND = basis::common::NOT_FOUND, //!< the file to launch was not found. - NONE_READY = basis::common::NONE_READY, //!< there was no data available. - PARTIAL = basis::common::PARTIAL, //!< only some of the bytes could be stored. - ACCESS_DENIED = basis::common::ACCESS_DENIED //!< the OS told us we could not. - }; - - basis::outcome health() const { return _persistent_result; } - //!< if the object constructed successfully, this returns OKAY. - /*!< otherwise an error occurred during the pipe creation or process fork. - */ - - bool running(); - //!< returns true if the application is still running. - /*!< if it exited on its own, then this will return false. */ - - int exit_value() const { return _exit_value; } - //!< returns the exit value from the app after it was launched. - /*!< this is only valid if the launch succeeded, the app ran, and now the - running() method is returning false because the application exited. */ - - int process_id() const { return _process_id; } - //!< the process id of the launched application. - /*!< this is only valid if the app is still running. */ - - void zap_program(); - //!< attempts to force a shutdown of the launched application. - /*!< this also closes out all of our pipes and handles. */ - - basis::outcome read(basis::byte_array &received); - //!< attempts to read bytes from the program's standard output. - /*!< if any bytes were found, OKAY is returned and the bytes are stored - in "received". */ - - basis::outcome write(const basis::byte_array &to_write, int &written); - //!< writes the bytes in "to_write" into the program's standard input. - /*!< OKAY is returned if all were successfully written. the number of - bytes that were actually sent to the program is put in "written". if only - a portion of the bytes could be written, then PARTIAL is returned. */ - - basis::outcome write(const basis::astring &to_write, int &written); - //!< sends a string "to_write" to the launched program's standard input. - - basis::outcome read_stderr(basis::byte_array &received); - //!< reads from the program's standard error stream similarly to read(). - - void close_input(); - //!< shuts down the standard input to the program. - /*!< this simulates when the program receives an end of file on its - main input. */ - - // internal use only... - - void std_thread_action(bool is_stdout); - //!< invoked by our threads when data becomes available. - /*!< if "is_stdout" is true, then that's the pipe we get data from. - otherwise we will try to get data from stderr. */ - -private: -#ifdef __UNIX__ - int _output_fds[2]; //!< file descriptors for parent's output. - int _input_fds[2]; //!< file descriptors for parent's input. - int _stderr_fds[2]; //!< file descriptors for standard error from child. -#endif -#ifdef __WIN32__ - void *_child_in, *_child_out, *_child_err; //!< child process pipes replace io. - void *_parent_in, *_parent_out, *_parent_err; //!< our access to the pipes. - void *_app_handle; //!< refers to the launched application. -#endif - basis::astring *_command; //!< the application that we'll start and switch I/O on. - basis::astring *_parms; //!< the parameters for the app we will launch. - basis::outcome _persistent_result; //!< this records failures during construction. - processes::ethread *_stdout_reader; //!< gets data from process's stdout. - processes::ethread *_stderr_reader; //!< gets data from process's stderr. - basis::byte_array *_stdout_queue; //!< holds data acquired from stdout. - basis::byte_array *_stderr_queue; //!< holds data acquired from stderr. - basis::mutex *_lock; //!< protects our queues. - int _process_id; //!< pid for the launched program. - int _exit_value; //!< stored once when we notice app is gone. - - basis::outcome create_pipes(); - //!< creates the three pipes that the child will be given as stdin, stdout and stderr. - - basis::outcome launch_program(int &new_process_id); - //!< launches the application using the previously established pipes. -}; - -} //namespace. - -#endif - diff --git a/core/library/application/shared_memory.cpp b/core/library/application/shared_memory.cpp deleted file mode 100644 index 4dab6191..00000000 --- a/core/library/application/shared_memory.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/*****************************************************************************\ -* * -* Name : shared_memory * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "shared_memory.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef __UNIX__ - #include - #include - #include - #include - #include - #include -#endif - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; - -namespace application { - -shared_memory::shared_memory(int size, const char *identity) -: _locking(new rendezvous(identity)), - _the_memory(NIL), - _valid(false), - _identity(new astring(identity)), - _size(size) -{ -// FUNCDEF("constructor"); - bool first_use = false; // assume already existing until told otherwise. - _locking->lock(); // force all others to wait on our finishing creation. -#ifdef __UNIX__ - int flag = O_RDWR | O_CREAT | O_EXCL; - // try to create the object if it doesn't exist yet, but fail if it does. - int mode = 0700; // rwx------ for just us. - _the_memory = shm_open(special_filename(identity).s(), flag, mode); - // create the shared memory object but fail if it already exists. - if (negative(_the_memory)) { - // failed to create the shared segment. try without forcing exclusivity. - flag = O_RDWR | O_CREAT; - _the_memory = shm_open(special_filename(identity).s(), flag, mode); - basis::un_int err = critical_events::system_error(); // get last error. - if (negative(_the_memory)) { - // definitely a failure now... - printf("error allocating shared segment for %s, was told %s.\n", - special_filename(identity).s(), critical_events::system_error_text(err).s()); - _the_memory = 0; - _locking->unlock(); // release lock before return. - return; - } - // getting to here means the memory was already allocated. so we're fine. - } else { - // the shared memory segment was just created this time. - int ret = ftruncate(_the_memory, size); - basis::un_int err = critical_events::system_error(); // get last error. - if (ret) { - printf("error truncating shared segment for %s, was told %s.", - special_filename(identity).s(), critical_events::system_error_text(err).s()); - } - first_use = true; - } - _valid = true; -#elif defined(__WIN32__) - _the_memory = ::CreateFileMapping((HANDLE)-1, NULL, PAGE_READWRITE, - 0, size, to_unicode_temp(identity)); - basis::un_int err = critical_events::system_error(); // get last error. - first_use = (err != ERROR_ALREADY_EXISTS); - if (!_the_memory) { - _locking->unlock(); - return; // not healthy. - } - _valid = true; -#else -//this is junk; simulates shared memory poorly. - #pragma message("simulating shared memory since unknown for this platform.") - if (!_bogus_shared_space().length()) { - _bogus_shared_space().reset(size); - first_use = true; - } - _the_memory = _bogus_shared_space().access(); - _valid = true; -#endif - if (first_use) { - // initialize the new memory to all zeros. - abyte *contents = locked_grab_memory(); - if (!contents) { - _valid = false; - _locking->unlock(); - return; - } - memset(contents, 0, size); - locked_release_memory(contents); // release the memory for now. - } - _locking->unlock(); -} - -shared_memory::~shared_memory() -{ -#ifdef __UNIX__ - if (_the_memory) { - close(int(_the_memory)); - shm_unlink(special_filename(identity()).s()); - } -#elif defined(__WIN32__) - ::CloseHandle(_the_memory); -#else - //hmmm: fix. - _the_memory = NIL; -#endif - WHACK(_locking); - WHACK(_identity); - _valid = false; -} - -const astring &shared_memory::identity() const { return *_identity; } - -astring shared_memory::special_filename(const astring &identity) -{ - astring shared_file = identity; - filename::detooth_filename(shared_file); - shared_file = astring("/tmp_") + "sharmem_" + shared_file; - return shared_file; -} - -astring shared_memory::unique_shared_mem_identifier(int sequencer) -{ - astring to_return("SMID-"); - to_return += a_sprintf("%d-%d-", application_configuration::process_id(), sequencer); - to_return += application_configuration::application_name(); - // replace file delimiters in the name with a safer character. - filename::detooth_filename(to_return); - return to_return; -} - -bool shared_memory::first_usage(abyte *contents, int max_compare) -{ - for (int i = 0; i < max_compare; i++) - if (contents[i] != 0) return false; - return true; -} - -abyte *shared_memory::lock() -{ - _locking->lock(); - return locked_grab_memory(); -} - -void shared_memory::unlock(abyte * &to_unlock) -{ - locked_release_memory(to_unlock); - _locking->unlock(); -} - -abyte *shared_memory::locked_grab_memory() -{ - FUNCDEF("locked_grab_memory") - abyte *to_return = NIL; - if (!_the_memory) return to_return; -#ifdef __UNIX__ - to_return = (abyte *)mmap(NIL, _size, PROT_READ | PROT_WRITE, - MAP_SHARED, int(_the_memory), 0); -#elif defined(__WIN32__) - to_return = (abyte *)::MapViewOfFile((HANDLE)_the_memory, FILE_MAP_ALL_ACCESS, - 0, 0, 0); -#else - to_return = (abyte *)_the_memory; -#endif - if (!to_return) { -// FUNCTION(func); -//not working yet. callstack tracker or whatever is hosed up. - throw(astring(astring(class_name()) + "::" + func + ": no data was accessible in shared space.")); - } - return to_return; -} - -void shared_memory::locked_release_memory(abyte * &to_unlock) -{ - if (!_the_memory || !to_unlock) return; -#ifdef __UNIX__ - munmap(to_unlock, _size); -#elif defined(__WIN32__) - ::UnmapViewOfFile(to_unlock); -#else -//uhh. -#endif -} - -} //namespace. - diff --git a/core/library/application/shared_memory.h b/core/library/application/shared_memory.h deleted file mode 100644 index 47c8a61c..00000000 --- a/core/library/application/shared_memory.h +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef SHARED_MEMORY_CLASS -#define SHARED_MEMORY_CLASS - -/*****************************************************************************\ -* * -* Name : shared_memory * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace application { - -//! Implements storage for memory that can be shared between threads. -/*! - Provides a means to create shared memory chunks and access them from - anywhere in a program or from cooperating programs. -*/ - -class shared_memory : public virtual basis::root_object -{ -public: - shared_memory(int size, const char *identity); - //!< a shared chunk of the "size" specified will be created. - /*!< it is named by the "identity" string. that "identity" uniquely - points to one shared chunk on this machine. note that if this object is - the first to create the chunk of memory, then all of the contents are - initialized to zero. this can be used to determine if the chunk needs - higher level, application-specific initialization. */ - - virtual ~shared_memory(); - //!< cleans up the shared bit of memory as far as we're concerned. - /*!< if some other instance still has it opened, then it isn't - destroyed for real yet. */ - - DEFINE_CLASS_NAME("shared_memory"); - - bool valid() const { return _valid; } - //!< this must be true for the shared_memory to be usable. - /*!< if it's false, then the memory chunk was never created. */ - - int size() const { return _size; } - //!< returns the size of the shared chunk of memory. - - const basis::astring &identity() const; - //!< provides a peek at the name that this chunk was constructed with. - - bool first_usage(basis::abyte *locked_memory, int max_compare); - //!< returns true if the "locked_memory" was just created. - /*!< that is assuming that a user of the shared memory will set the first - "max_compare" bytes to something other than all zeros. this is really - just a test of whether bytes zero through bytes "max_compare" - 1 are - currently zero, causing a return of true. seeing anything besides zero - causes a false return. */ - - basis::abyte *lock(); - //!< locks the shared memory and returns a pointer to the storage. - /*!< the synchronization supported is only within this program; this type - of shared memory is not intended for access from multiple processes, - just for access from multiple threads in the same app. */ - - void unlock(basis::abyte * &to_unlock); - //!< returns control of the shared memory so others can access it. - /*!< calls to lock() must be paired up with calls to unlock(). */ - - static basis::astring unique_shared_mem_identifier(int sequencer); - //!< returns a unique identifier for a shared memory chunk. - /*!< the id returned is unique on this host for this particular process - and application, given a "sequencer" number that is up to the application - to keep track of uniquely. the values from -100 through -1 are reserved - for hoople library internals. */ - -private: - processes::rendezvous *_locking; //!< protects our shared memory. -#ifdef __UNIX__ - int _the_memory; //!< OS index of the memory. -#elif defined(__WIN32__) - void *_the_memory; //!< OS pointer to the memory. -#endif - bool _valid; //!< true if the memory creation succeeded. - basis::astring *_identity; //!< holds the name we were created with. - int _size; //!< size of memory chunk. - - // these do the actual work of getting the memory. - basis::abyte *locked_grab_memory(); - void locked_release_memory(basis::abyte * &to_unlock); - - static basis::astring special_filename(const basis::astring &identity); - //!< provides the name for our shared memory file, if needed. - - // forbidden. - shared_memory(const shared_memory &); - shared_memory &operator =(const shared_memory &); -}; - -} //namespace. - - -#endif // outer guard. - diff --git a/core/library/application/singleton_application.cpp b/core/library/application/singleton_application.cpp deleted file mode 100644 index 14b3fa6e..00000000 --- a/core/library/application/singleton_application.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/*****************************************************************************\ -* * -* Name : singleton_application * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "singleton_application.h" - -#include -#include - -using namespace basis; -using namespace filesystem; -using namespace processes; - -namespace application { - -singleton_application::singleton_application(const astring &application_name, - const astring &user_name) -: c_initial_try(0), - _app_lock(new rendezvous(filename(application_name).basename().raw() - + "__" + user_name)), - _got_lock(false) -{ -} - -singleton_application::~singleton_application() -{ - release_lock(); - WHACK(_app_lock); -} - -bool singleton_application::already_running() -{ return !allow_this_instance(); } - -bool singleton_application::already_tried() const -{ return c_initial_try > 0; } - -void singleton_application::release_lock() -{ - if (_got_lock) { - _app_lock->unlock(); - _got_lock = false; - } -} - -bool singleton_application::allow_this_instance() -{ - if (_got_lock) return true; // already grabbed it. - - if (!already_tried()) { - _got_lock = _app_lock->lock(rendezvous::IMMEDIATE_RETURN); - if (_got_lock) c_initial_try = 1; // succeeded in locking. - else c_initial_try = 2; // failure. - } - - return _got_lock; -} - -} //namespace. - diff --git a/core/library/application/singleton_application.h b/core/library/application/singleton_application.h deleted file mode 100644 index f086809e..00000000 --- a/core/library/application/singleton_application.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef SINGLETON_APPLICATION_CLASS -#define SINGLETON_APPLICATION_CLASS - -/*****************************************************************************\ -* * -* Name : singleton_application * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! Implements an application lock to ensure only one is running at once. -/*! - This encapsulates the code used to ensure that only one copy of an - application is running at a time. It can either be made specific to a - user (so that user can run only one at a time) or made global to the entire - machine. -*/ - -#include -#include -#include - -namespace application { - -class singleton_application -{ -public: - singleton_application(const basis::astring &application_name, - const basis::astring &user_name = basis::astring::empty_string()); - //!< constructs a singleton guardian for the "application_name". - /*!< if the "user_name" is non-empty, then it will be used as part of - the lock name. this ensures that separate users can still run the - application at the same time, which is especially important for terminal - servers. for a lock that should affect the whole machine and ignore - users, the "user_name" must be empty. */ - - virtual ~singleton_application(); - - DEFINE_CLASS_NAME("singleton_application"); - - bool already_tried() const; - //!< returns true if the singleton has already tried to lock. - - bool allow_this_instance(); - //!< the application must check this before starting up. - /*!< if this returns false, then the application must exit immediately or - it will be violating the singleton rule. */ - - bool already_running(); - //!< returns false if this program is not already running. - /*!< this is just the opposite of the allow_this_instance() method. */ - - void release_lock(); - //!< let's go of the application lock, if we had previously gotten it. - /*!< this should only be called when the application is about to exit. */ - -private: - int c_initial_try; //!< has the initial locking attempt been made? - /* if c_initial_try is zero, no attempt made yet. if it's 1, then tried - and succeeded. if it's greater than one, then tried and failed. */ - processes::rendezvous *_app_lock; //!< used to lock out other instances. - bool _got_lock; //!< records whether we successfully got the lock or not. -}; - -} //namespace. - -#endif - diff --git a/core/library/application/window_classist.h b/core/library/application/window_classist.h deleted file mode 100644 index 5b9a1ccb..00000000 --- a/core/library/application/window_classist.h +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef WINDOW_CLASSIST_CLASS -#define WINDOW_CLASSIST_CLASS - -/*****************************************************************************\ -* * -* Name : window_classist * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! An add-in file providing window class registration and a window procedure. -/*! - This file makes it easier to add a very simple window to any console or - win32 application that might need it (possibly because the app does not - create any windows itself, but for crazy insane reasons, a window is still - needed by an external agent, ahem installshield). It implements a very - important part of this process, which is setting a window procedure and - registering a window class. Sometime in 2005 or 2006, a windows update - came through that made these formerly optional practices mandatory (and - broke many of our applications that created windows without a window - procedure or class registration). That occurrence prompted the creation - of this class which tries to provide the bare minimum needed to make - things work again. - - Example Usage: - - // include our code file to embed the window procedure and register class - // methods in whoever needs them. this should only be needed once per - // program. - - #include - - // create our simple window... - - basis::astring window_title = "my_freaky_window"; - basis::astring class_name = "jumbo_stompy_update_crudburger"; - - window_handle f_window = create_simplistic_window(window_title, class_name); - - // and then much later, after the window is no longer needed... - - whack_simplistic_window(f_window); - -*/ - -#include "windoze_helper.h" - -#include - -namespace application { - -#ifndef __WIN32__ - -// this is a placeholder implementation for other platforms. -window_handle create_simplistic_window(const basis::astring &formal(window_title), - const basis::astring &formal(class_name)) { return NIL; } -void whack_simplistic_window(window_handle formal(f_window)) {} - -#else - -//! this is the very simple window procedure used by register_class() below. - -LRESULT CALLBACK window_procedure(HWND hWnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - - switch (message) { - case WM_COMMAND: { - int identifier, event; - identifier = LOWORD(wParam); - event = HIWORD(wParam); - return DefWindowProc(hWnd, message, wParam, lParam); - break; - } - case WM_PAINT: { - HDC hdc; - PAINTSTRUCT ps; - hdc = BeginPaint(hWnd, &ps); - // hmmm: Add any drawing code here... - EndPaint(hWnd, &ps); - break; - } - case WM_DESTROY: { - PostQuitMessage(0); - break; - } - default: { - return DefWindowProc(hWnd, message, wParam, lParam); - } - } - return 0; -} - -//! returns the registered class as a windows atom. - -ATOM register_class(const basis::astring &name) -{ - WNDCLASSEX wcex; - wcex.cbSize = sizeof(WNDCLASSEX); - - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.lpfnWndProc = (WNDPROC)window_procedure; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = GET_INSTANCE_HANDLE(); - wcex.hIcon = 0; - wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); - wcex.lpszMenuName = 0; - basis::to_unicode_persist(temp_class, name); - wcex.lpszClassName = temp_class; - wcex.hIconSm = 0; - - return RegisterClassEx(&wcex); -} - -window_handle create_simplistic_window(const basis::astring &window_title, - const basis::astring &class_name) -{ - register_class(class_name); - window_handle f_window = CreateWindow(basis::to_unicode_temp(class_name), - basis::to_unicode_temp(window_title), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, - 0, CW_USEDEFAULT, 0, NIL, NIL, GET_INSTANCE_HANDLE(), NIL); - ShowWindow(f_window, SW_HIDE); - UpdateWindow(f_window); - return f_window; -} - -void whack_simplistic_window(window_handle f_window) -{ - SendMessage(f_window, WM_CLOSE, NIL, NIL); -//hmmm: is this enough? -} - -#endif // win32 - -} // namespace. - -#endif // outer guard - diff --git a/core/library/application/windoze_helper.cpp b/core/library/application/windoze_helper.cpp deleted file mode 100644 index c9c29109..00000000 --- a/core/library/application/windoze_helper.cpp +++ /dev/null @@ -1,934 +0,0 @@ -/*****************************************************************************\ -* * -* Name : windoze_helper * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "windoze_helper.h" -///#include "array.h" -///#include "build_configuration.h" -///#include "convert_utf.h" -///#include "function.h" -///#include "mutex.h" -///#include "portable.h" -///#include "set.h" -///#include "version_record.h" -#include - -///#include -///#include - -/* -#ifdef __UNIX__ - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif -#ifdef __XWINDOWS__ -//hmmm: need code for the wait cursor stuff. -#endif -#ifdef __WIN32__ - #include - #include - #include - #include -#endif -*/ - -using namespace basis; -using namespace structures; -using namespace configuration; - -#undef static_class_name -#define static_class_name() "windoze_helper" - -/* -//#define DEBUG_PORTABLE - // uncomment for noisy debugging. - -//#define DEBUG_UPTIME - // uncomment to get noisier reporting about system and rolling uptime. - -//#define JUMP_TIME_49_DAYS - // uncomment to make our uptimes start just before the 32 bit uptime rollover. -//#define JUMP_TIME_497_DAYS - // uncomment to make our uptimes start just before the jiffies rollover. - -//#define ENABLE_ROLLOVER_BUG - // uncomment to get old behavior back where the uptime was not rolling - // over properly. this turns off our remainder calculation and leaves the - // conversion as a simple cast, which will fail and get stuck at 2^32-1. - -#define SUPPORT_SHELL_EXECUTE - // if this is not commented out, then the ShellExecute version of launch_ - // -process() is available. when commented out, ShellExecute is turned off. - // disabling this support is the most common because the ShellExecute method - // in win32 was only supported for wk203 and wxp, that is only after - // windows2000 was already available. since nt and w2k don't support this, - // we just usually don't mess with it. it didn't answer a single one of our - // issues on windows vista (wfista) anyway, so it's not helping. - -// ensure we always have debugging turned on if the jump is enabled. -#ifdef JUMP_TIME_49_DAYS - #undef DEBUG_UPTIME - #define DEBUG_UPTIME -#endif -#ifdef JUMP_TIME_497_DAYS - #undef DEBUG_UPTIME - #define DEBUG_UPTIME -#endif -// the JUMP..DAYS macros are mutually exclusive. use none or one, not both. -#ifdef JUMP_TIME_497_DAYS - #ifdef JUMP_TIME_49_DAYS - #error One cannot use both 497 day and 49 day bug inducers - #endif -#endif -#ifdef JUMP_TIME_49_DAYS - #ifdef JUMP_TIME_497_DAYS - #error One cannot use both 49 day and 497 day bug inducers - #endif -#endif -*/ - - -namespace application { - -/* -mutex BASIS_EXTERN &__uptime_synchronizer(); - // used by our low-level uptime methods to protect singleton variables. -mutex BASIS_EXTERN &__process_synchronizer(); - // used for synchronizing our records of child processes. -#ifdef __UNIX__ -int_set BASIS_EXTERN &__our_kids(); - // the static list of processes we've started. -#endif - -const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; - // maximum command line that we'll deal with here. - -#ifdef __UNIX__ - const char *UPTIME_REPORT_FILE = "/tmp/uptime_report.log"; -#endif -#ifdef __WIN32__ - const char *UPTIME_REPORT_FILE = "c:/uptime_report.log"; -#endif - -#undef LOG -#define LOG(s) STAMPED_EMERGENCY_LOG(program_wide_logger(), s); - -#define COMPLAIN(to_print) { \ - guards::write_to_console((isprintf("basis/portable::%s: ", func) + to_print).s()); \ -} - -static const double __rollover_point = 2.0 * double(MAXINT); - // this number is our rollover point for 32 bit integers. - -double rolling_uptime() -{ - auto_synchronizer l(__uptime_synchronizer()); - // protect our rollover records. - - static u_int __last_ticks = 0; - static int __rollovers = 0; - - u_int ticks_up = system_uptime(); - // acquire the current uptime as a 32 bit unsigned int. - - if (ticks_up < __last_ticks) { - // rollover happened. increment our tracker. - __rollovers++; - } - __last_ticks = ticks_up; - - double to_return = double(__rollovers) * __rollover_point + double(ticks_up); - -#ifdef DEBUG_UPTIME - #ifdef __WIN32__ - static FILE *__outfile = fopen(UPTIME_REPORT_FILE, "a+b"); - static double old_value = 0; - if (absolute_value(old_value - to_return) > 9.9999) { - // only report when the time changes by more than 10 ms. - fprintf(__outfile, "-> uptime=%.0f\n", to_return); - fflush(__outfile); - old_value = to_return; - if (__rollover_point - to_return <= 40.00001) { - fprintf(__outfile, "---> MAXIMUM UPTIME SOON!\n"); - fflush(__outfile); - } - } - #endif -#endif - - return to_return; -} - -u_int system_uptime() -{ -#ifdef __WIN32__ - return timeGetTime(); -#else - auto_synchronizer l(__uptime_synchronizer()); - - static clock_t __ctps = sysconf(_SC_CLK_TCK); // clock ticks per second. - static const double __multiplier = 1000.0 / double(__ctps); - // the multiplier gives us our full range for the tick counter. - -#ifdef DEBUG_UPTIME - static FILE *__outfile = fopen(UPTIME_REPORT_FILE, "wb"); - if (__multiplier - u_int(__multiplier) > 0.000001) { - fprintf(__outfile, "uptime multiplier is " - "non-integral (%f)!\n", __multiplier); - fflush(__outfile); - } -#endif - - // read uptime info from the OS. - tms uptime; - u_int real_ticks = times(&uptime); - -#ifdef JUMP_TIME_497_DAYS - static u_int old_value_497 = 0; - bool report_497 = (absolute_value(real_ticks - old_value_497) > 99); - if (report_497) { - old_value_497 = real_ticks; // update before changing it. - fprintf(__outfile, "pre kludge497 tix=%u\n", real_ticks); - fflush(__outfile); - } - real_ticks += u_int(49.0 * double(DAY_ms) + 17.0 * double(HOUR_ms)); - if (report_497) { - fprintf(__outfile, "post kludge497 tix=%u\n", real_ticks); - fflush(__outfile); - } -#endif - - // now turn this into the number of milliseconds. - double ticks_up = (double)real_ticks; - ticks_up = ticks_up * __multiplier; // convert to time here. - -#ifdef JUMP_TIME_497_DAYS - #ifndef ENABLE_ROLLOVER_BUG - // add in additional time so we don't have to wait forever. we make the - // big number above rollover, but that's still got 27.5 or so minutes before - // we really rollover. so we add that part of the fraction (lost in the - // haze of the multiplier) in here. we don't add this in unless they are - // not exercising the rollover bug, because we already know that the 497 - // day bug will show without the addition. but when we're already fixing - // the uptime, we jump time a bit forward so we only have to wait a couple - // minutes instead of 27. - ticks_up += 25.0 * MINUTE_ms; - #endif -#endif - -#ifdef JUMP_TIME_49_DAYS - static u_int old_value_49 = 0; - bool report_49 = (absolute_value(u_int(ticks_up) - old_value_49) > 999); - if (report_49) { - old_value_49 = u_int(ticks_up); // update before changing it. - fprintf(__outfile, "pre kludge49 up=%f\n", ticks_up); - fflush(__outfile); - } - ticks_up += 49.0 * double(DAY_ms) + 17.0 * double(HOUR_ms); - if (report_49) { - fprintf(__outfile, "post kludge49 up=%f\n", ticks_up); - fflush(__outfile); - } -#endif - -#ifndef ENABLE_ROLLOVER_BUG - // fix the return value if is has actually gone over the 2^32 limit for uint. - // casting a double larger than 2*MAXINT to a u_int on some platforms does - // not calculate a rolled-over value, but instead leaves the int at 2*MAXINT. - // thus we make sure it will be correct, as long as there are no more than - // 2^32-1 rollovers, which would be about 584,542 millenia. it's unlikely - // earth will last that long, so this calculation seems safe. - u_int divided = u_int(ticks_up / __rollover_point); - double to_return = ticks_up - (double(divided) * __rollover_point); -#else - // we use the previous version of this calculation, which expected a u_int - // to double conversion to provide a modulo operation rather than just leaving - // the u_int at its maximum value (2^32-1). however, that expectation is not - // guaranteed on some platforms (e.g., ARM processor with floating point - // emulation) and thus it becomes a bug around 49 days and 17 hours into - // OS uptime because the value gets stuck at 2^32-1 and never rolls over. - double to_return = ticks_up; -#endif - -#ifdef DEBUG_UPTIME - static u_int old_value = 0; - int to_print = int(u_int(to_return)); - if (absolute_value(int(old_value) - to_print) > 9) { - // only report when the time changes by more than 10 ms. - fprintf(__outfile, "-> uptime=%u\n", to_print); - fflush(__outfile); - old_value = u_int(to_print); - if (__rollover_point - to_return <= 40.00001) { - fprintf(__outfile, "---> MAXIMUM UPTIME SOON!\n"); - fflush(__outfile); - } - } -#endif - - return u_int(to_return); -#endif -} - -void sleep_ms(u_int msec) -{ -#ifdef __UNIX__ - usleep(msec * 1000); -#endif -#ifdef __WIN32__ - Sleep(msec); -#endif -} - -istring null_device() -{ -#ifdef __WIN32__ - return "null:"; -#else - return "/dev/null"; -#endif -} - -#ifdef __WIN32__ -bool event_poll(MSG &message) -{ - message.hwnd = 0; - message.message = 0; - message.wParam = 0; - message.lParam = 0; - if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE)) - return false; - TranslateMessage(&message); - DispatchMessage(&message); - return true; -} -#endif - -// makes a complaint about a failure. -#ifndef EMBEDDED_BUILD - #define COMPLAIN_QUERY(to_print) { \ - unlink(tmpfile.s()); \ - COMPLAIN(to_print); \ - } -#else - #define COMPLAIN_QUERY(to_print) { \ - COMPLAIN(to_print); \ - } -#endif - -#ifdef __UNIX__ -istring get_cmdline_from_proc() -{ - FUNCDEF("get_cmdline_from_proc"); - isprintf cmds_filename("/proc/%d/cmdline", portable::process_id()); - FILE *cmds_file = fopen(cmds_filename.s(), "r"); - if (!cmds_file) { - COMPLAIN("failed to open our process's command line file.\n"); - return "unknown"; - } - size_t size = 2000; - char *buff = new char[size + 1]; - ssize_t chars_read = getline(&buff, &size, cmds_file); - // read the first line, giving ample space for how long it might be. - fclose(cmds_file); // drop the file again. - if (!chars_read || negative(chars_read)) { - COMPLAIN("failed to get any characters from our process's cmdline file.\n"); - return "unknown"; - } - istring buffer = buff; - delete [] buff; - // clean out quote characters from the name. - for (int i = buffer.length() - 1; i >= 0; i--) { - if (buffer[i] == '"') buffer.zap(i, i); - } - return buffer; -} - -// deprecated; better to use the /proc/pid/cmdline file. -istring query_for_process_info() -{ - FUNCDEF("query_for_process_info"); - istring to_return = "unknown"; - // we ask the operating system about our process identifier and store - // the results in a temporary file. - chaos rando; - isprintf tmpfile("/tmp/proc_name_check_%d_%d.txt", portable::process_id(), - rando.inclusive(0, 128000)); - isprintf cmd("ps h --format \"%%a\" %d >%s", portable::process_id(), - tmpfile.s()); - // run the command to locate our process info. - int sysret = system(cmd.s()); - if (negative(sysret)) { - COMPLAIN_QUERY("failed to run ps command to get process info"); - return to_return; - } - // open the output file for reading. - FILE *output = fopen(tmpfile.s(), "r"); - if (!output) { - COMPLAIN_QUERY("failed to open the ps output file"); - return to_return; - } - // read the file's contents into a string buffer. - char buff[MAXIMUM_COMMAND_LINE]; - size_t size_read = fread(buff, 1, MAXIMUM_COMMAND_LINE, output); - if (size_read > 0) { - // success at finding some text in the file at least. - while (size_read > 0) { - const char to_check = buff[size_read - 1]; - if ( !to_check || (to_check == '\r') || (to_check == '\n') - || (to_check == '\t') ) - size_read--; - else break; - } - to_return.reset(istring::UNTERMINATED, buff, size_read); - } else { - // couldn't read anything. - COMPLAIN_QUERY("could not read output of process list"); - } - unlink(tmpfile.s()); - return to_return; -} -#endif - -// used as a return value when the name cannot be determined. -#ifndef EMBEDDED_BUILD - #define SET_BOGUS_NAME(error) { \ - COMPLAIN(error); \ - if (output) { \ - fclose(output); \ - unlink(tmpfile.s()); \ - } \ - istring home_dir = env_string("HOME"); \ - to_return = home_dir + "/failed_to_determine.exe"; \ - } -#else - #define SET_BOGUS_NAME(error) { \ - COMPLAIN(error); \ - to_return = "unknown"; \ - } -#endif - -istring application_name() -{ - FUNCDEF("application_name"); - istring to_return; -#ifdef __UNIX__ - to_return = get_cmdline_from_proc(); -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetModuleFileName(NIL, low_buff, MAX_ABS_PATH - 1); - istring buff = from_unicode_temp(low_buff); - buff.to_lower(); // we lower-case the name since windows seems to UC it. - to_return = buff; -#elif defined(EMBEDDED_BUILD) - SET_BOGUS_NAME("embedded_exe"); -#else - #pragma error("hmmm: no means of finding app name is implemented.") - SET_BOGUS_NAME("not_implemented_for_this_OS"); -#endif - return to_return; -} - -istring module_name(const void *module_handle) -{ -#ifdef __UNIX__ -//hmmm: implement module name for linux if that makes sense. - if (module_handle) {} - return application_name(); -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1); - istring buff = from_unicode_temp(low_buff); - buff.to_lower(); - return buff; -#else - #pragma message("module_name unknown for this operating system.") - return application_name(); -#endif -} - -u_int system_error() -{ -#if defined(__UNIX__) - return errno; -#elif defined(__WIN32__) - return GetLastError(); -#else - #pragma error("hmmm: no code for error number for this operating system") - return 0; -#endif -} - -istring system_error_text(u_int to_name) -{ -#if defined(__UNIX__) - return strerror(to_name); -#elif defined(__WIN32__) - char error_text[1000]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NIL, to_name, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)error_text, - sizeof(error_text) - 1, NIL); - istring to_return = error_text; - // trim off the ridiculous carriage return they add. - while ( (to_return[to_return.end()] == '\r') - || (to_return[to_return.end()] == '\n') ) - to_return.zap(to_return.end(), to_return.end()); - return to_return; -#else - #pragma error("hmmm: no code for error text for this operating system") - return ""; -#endif -} - -#ifdef __WIN32__ - -bool is_address_valid(const void *address, int size_expected, bool writable) -{ - return address && !IsBadReadPtr(address, size_expected) - && !(writable && IsBadWritePtr((void *)address, size_expected)); -} - -#endif // win32 - -version get_OS_version() -{ - version to_return; -#ifdef __UNIX__ - utsname kernel_parms; - uname(&kernel_parms); - to_return = version(kernel_parms.release); -#elif defined(__WIN32__) - OSVERSIONINFO info; - info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - ::GetVersionEx(&info); - to_return = version(isprintf("%u.%u.%u.%u", u_short(info.dwMajorVersion), - u_short(info.dwMinorVersion), u_short(info.dwPlatformId), - u_short(info.dwBuildNumber))); -#elif defined(EMBEDDED_BUILD) - // no version support. -#else - #pragma error("hmmm: need version info for this OS!") -#endif - return to_return; -} - -// for non-win32 and non-unix OSes, this might not work. -#if defined(__UNIX__) || defined(__WIN32__) - u_int process_id() { return getpid(); } -#else - #pragma error("hmmm: need process id implementation for this OS!") - u_int process_id() { return 0; } -#endif - -istring current_directory() -{ - istring to_return; -#ifdef __UNIX__ - char buff[MAX_ABS_PATH]; - getcwd(buff, MAX_ABS_PATH - 1); - to_return = buff; -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetCurrentDirectory(MAX_ABS_PATH, low_buff); - to_return = from_unicode_temp(low_buff); -#else - #pragma error("hmmm: need support for current directory on this OS.") - to_return = "."; -#endif - return to_return; -} - -istring env_string(const istring &variable_name) -{ -#ifdef __WIN32__ - char *value = getenv(variable_name.upper().observe()); - // dos & os/2 require upper case for the name, so we just do it that way. -#else - char *value = getenv(variable_name.observe()); - // reasonable OSes support mixed-case environment variables. -#endif - istring to_return; - if (value) - to_return = istring(value); - return to_return; -} - -bool set_environ(const istring &variable_name, const istring &value) -{ - int ret = 0; -#ifdef __WIN32__ - ret = putenv((variable_name + "=" + value).s()); -#else - ret = setenv(variable_name.s(), value.s(), true); -#endif - return !ret; -} - -timeval fill_timeval_ms(int duration) -{ - timeval time_out; // timeval has tv_sec=seconds, tv_usec=microseconds. - if (!duration) { - // duration is immediate for the check; just a quick poll. - time_out.tv_sec = 0; - time_out.tv_usec = 0; -#ifdef DEBUG_PORTABLE -// LOG("no duration specified"); -#endif - } else { - // a non-zero duration means we need to compute secs and usecs. - time_out.tv_sec = duration / 1000; - // set the number of seconds from the input in milliseconds. - duration -= time_out.tv_sec * 1000; - // now take out the chunk we've already recorded as seconds. - time_out.tv_usec = duration * 1000; - // set the number of microseconds from the remaining milliseconds. -#ifdef DEBUG_PORTABLE -// LOG(isprintf("duration of %d ms went to %d sec and %d usec.", duration, -// time_out.tv_sec, time_out.tv_usec)); -#endif - } - return time_out; -} - -//hmmm: this doesn't seem to account for quoting properly at all? -basis::char_star_array break_line(istring &app, const istring ¶meters) -{ - basis::char_star_array to_return; - int_array posns; - int num = 0; - // find the positions of the spaces and count them. - for (int j = 0; j < parameters.length(); j++) { - if (parameters[j] == ' ') { - num++; - posns += j; - } - } - // first, add the app name to the list of parms. - to_return += new char[app.length() + 1]; - app.stuff(to_return[0], app.length()); - int last_posn = 0; - // now add each space-separated parameter to the list. - for (int i = 0; i < num; i++) { - int len = posns[i] - last_posn; - to_return += new char[len + 1]; - parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len); - last_posn = posns[i] + 1; - } - // catch anything left after last separator. - if (last_posn < parameters.length() - 1) { - int len = parameters.length() - last_posn; - to_return += new char[len + 1]; - parameters.substring(last_posn, parameters.length() - 1) - .stuff(to_return[to_return.last()], len); - } - // add the sentinel to the list of strings. - to_return += NIL; -#ifdef DEBUG_PORTABLE - for (int q = 0; to_return[q]; q++) { - printf("%d: %s\n", q, to_return[q]); - } -#endif - // now a special detour; fix the app name to remove quotes, which are - // not friendly to pass to exec. - if (app[0] == '"') app.zap(0, 0); - if (app[app.end()] == '"') app.zap(app.end(), app.end()); - return to_return; -} - -#ifdef __UNIX__ - -void exiting_child_signal_handler(int sig_num) -{ - FUNCDEF("exiting_child_signal_handler"); - if (sig_num != SIGCHLD) { - // uhhh, this seems wrong. - } - auto_synchronizer l(__process_synchronizer()); - for (int i = 0; i < __our_kids().length(); i++) { - int status; - pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG); - if ( (exited == -1) || (exited == __our_kids()[i]) ) { - // negative one denotes an error, which we are going to assume means the - // process has exited via some other method than our wait. if the value - // is the same as the process we waited for, that means it exited. - __our_kids().zap(i, i); - i--; - } else if (exited != 0) { - // zero would be okay; this result we do not understand. -#ifdef DEBUG_PORTABLE - printf("unknown result %d waiting for process %d", exited, - __our_kids()[i]); -#endif - } - } -} -#endif - -u_int launch_process(const istring &app_name_in, const istring &command_line, - int flag, u_int &child_id) -{ - child_id = 0; - istring app_name = app_name_in; - if (app_name[0] != '"') - app_name.insert(0, "\""); - if (app_name[app_name.end()] != '"') - app_name += "\""; -#ifdef __UNIX__ - // unix / linux implementation. - if (flag & RETURN_IMMEDIATELY) { - // they want to get back right away. - pid_t kid_pid = fork(); -#ifdef DEBUG_PORTABLE - printf("launch fork returned %d\n", kid_pid); -#endif - if (!kid_pid) { - // this is the child; we now need to launch into what we were asked for. -#ifdef DEBUG_PORTABLE - printf((isprintf("process %d execing ", process_id()) + app_name - + " parms " + command_line + "\n").s()); -#endif - basis::char_star_array parms = break_line(app_name, command_line); - execv(app_name.s(), parms.observe()); - // oops. failed to exec if we got to here. -#ifdef DEBUG_PORTABLE - printf((isprintf("child of fork (pid %d) failed to exec, " - "error is ", process_id()) + system_error_text(system_error()) - + "\n").s()); -#endif - exit(0); // leave since this is a failed child process. - } else { - // this is the parent. let's see if the launch worked. - if (kid_pid == -1) { - // failure. - u_int to_return = system_error(); -#ifdef DEBUG_PORTABLE - printf((isprintf("parent %d is returning after failing to create, " - "error is ", process_id()) + system_error_text(to_return) - + "\n").s()); -#endif - return to_return; - } else { - // yes, launch worked okay. - child_id = kid_pid; - { - auto_synchronizer l(__process_synchronizer()); - __our_kids() += kid_pid; - } - // hook in our child exit signal handler. - signal(SIGCHLD, exiting_child_signal_handler); - -#ifdef DEBUG_PORTABLE - printf((isprintf("parent %d is returning after successfully " - "creating %d ", process_id(), kid_pid) + app_name - + " parms " + command_line + "\n").s()); -#endif - return 0; - } - } - } else { - // assume they want to wait. - return system((app_name + " " + command_line).s()); - } -#elif defined(__WIN32__) - -//checking on whether we have admin rights for the launch. -//if (IsUserAnAdmin()) { -// MessageBox(0, (istring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); -//} else { -// MessageBox(0, (istring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); -//} - - PROCESS_INFORMATION process_info; - ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); - -#ifdef SUPPORT_SHELL_EXECUTE - if (flag & SHELL_EXECUTE) { - // new code for using shell execute method--required on vista for proper - // launching with the right security tokens. - int show_cmd = 0; - if (flag & HIDE_APP_WINDOW) { - // magic that hides a console window for mswindows. - show_cmd = SW_HIDE; - } else { - show_cmd = SW_SHOWNORMAL; - } - - SHELLEXECUTEINFO exec_info; - ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO)); - exec_info.cbSize = sizeof(SHELLEXECUTEINFO); - exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info. - | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs. - exec_info.hwnd = GetDesktopWindow(); -//hmmm: is get desktop window always appropriate? - to_unicode_persist(temp_verb, "open"); -//does "runas" work on xp also? or anywhere? - exec_info.lpVerb = temp_verb; - to_unicode_persist(temp_file, app_name); - exec_info.lpFile = temp_file; - to_unicode_persist(temp_parms, command_line); - exec_info.lpParameters = temp_parms; - exec_info.nShow = show_cmd; -// exec_info.hProcess = &process_info; - - BOOL worked = ShellExecuteEx(&exec_info); - if (!worked) - return system_error(); - // copy out the returned process handle. - process_info.hProcess = exec_info.hProcess; - process_info.dwProcessId = GetProcessId(exec_info.hProcess); - } else { -#endif //shell exec - // standard windows implementation using CreateProcess. - STARTUPINFO startup_info; - ZeroMemory(&startup_info, sizeof(STARTUPINFO)); - startup_info.cb = sizeof(STARTUPINFO); - int create_flag = 0; - if (flag & HIDE_APP_WINDOW) { - // magic that hides a console window for mswindows. -// version ver = portable::get_OS_version(); -// version vista_version(6, 0); -// if (ver < vista_version) { -// // we suspect that this flag is hosing us in vista. - create_flag = CREATE_NO_WINDOW; -// } - } - istring parms = app_name + " " + command_line; - bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false, - create_flag, NIL, NIL, &startup_info, &process_info); - if (!success) - return system_error(); - // success then, merge back into stream. - -#ifdef SUPPORT_SHELL_EXECUTE - } -#endif //shell exec - - // common handling for CreateProcess and ShellExecuteEx. - child_id = process_info.dwProcessId; - u_long retval = 0; - if (flag & AWAIT_VIA_POLLING) { - // this type of waiting is done without blocking on the process. - while (true) { - MSG msg; - event_poll(msg); - // check if the process is gone yet. - BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval); - if (!ret) { - break; - } else { - // if they aren't saying it's still active, then we will leave. - if (retval != STILL_ACTIVE) - break; - } - sleep_ms(14); - } - } else if (flag & AWAIT_APP_EXIT) { - // they want to wait for the process to exit. - WaitForInputIdle(process_info.hProcess, INFINITE); - WaitForSingleObject(process_info.hProcess, INFINITE); - GetExitCodeProcess(process_info.hProcess, &retval); - } - // drop the process and thread handles. - if (process_info.hProcess) - CloseHandle(process_info.hProcess); - if (process_info.hThread) - CloseHandle(process_info.hThread); - return (u_int)retval; -#else - #pragma error("hmmm: launch_process: no implementation for this OS.") -#endif - return 0; -} - -//////////////////////////////////////////////////////////////////////////// - -#ifdef __UNIX__ - -char *itoa(int to_convert, char *buffer, int radix) -{ - // slow implementation; may need enhancement for serious speed. - // ALSO: only supports base 10 and base 16 currently. - const char *formatter = "%d"; - if (radix == 16) formatter = "%x"; - isprintf printed(formatter, to_convert); - printed.stuff(buffer, printed.length() + 1); - return buffer; -} - -#endif -*/ - -#ifdef __WIN32__ - -/* -void show_wait_cursor() { SetCursor(LoadCursor(NULL, IDC_WAIT)); } - -void show_normal_cursor() { SetCursor(LoadCursor(NULL, IDC_ARROW)); } - -istring rc_string(UINT id, application_instance instance) -{ - flexichar temp[MAX_ABS_PATH + 1]; - int ret = LoadString(instance, id, temp, MAX_ABS_PATH); - if (!ret) return istring(); - return istring(from_unicode_temp(temp)); -} - -istring rc_string(u_int id) { return rc_string(id, GET_INSTANCE_HANDLE()); } -*/ - -const char *opsystem_name(known_operating_systems which) -{ - switch (which) { - case WIN_95: return "WIN_95"; - case WIN_NT: return "WIN_NT"; - case WIN_2K: return "WIN_2K"; - case WIN_XP: return "WIN_XP"; - case WIN_SRV2K3: return "WIN_SRV2K3"; - case WIN_VISTA: return "WIN_VISTA"; - case WIN_SRV2K8: return "WIN_SRV2K8"; - default: return "UNKNOWN_OS"; - } -} - -known_operating_systems determine_OS() -{ - version osver = application_configuration::get_OS_version(); - if ( (osver.v_major() == 4) && (osver.v_minor() == 0) ) { - if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) return WIN_95; - if (osver.v_revision() == VER_PLATFORM_WIN32_NT) return WIN_NT; - } else if ( (osver.v_major() == 5) && (osver.v_minor() == 0) ) { - return WIN_2K; - } else if ( (osver.v_major() == 5) && (osver.v_minor() == 1) ) { - return WIN_XP; - } else if ( (osver.v_major() == 5) && (osver.v_minor() == 2) ) { - return WIN_SRV2K3; - } else if ( (osver.v_major() == 6) && (osver.v_minor() == 0) ) { - return WIN_VISTA; - } else if ( (osver.v_major() == 6) && (osver.v_minor() == 1) ) { - return WIN_SRV2K8; - } - return UNKNOWN_OS; -} - -#endif // win32 - -} // namespace. - -#undef static_class_name - diff --git a/core/library/application/windoze_helper.h b/core/library/application/windoze_helper.h deleted file mode 100644 index a02b2965..00000000 --- a/core/library/application/windoze_helper.h +++ /dev/null @@ -1,246 +0,0 @@ -#ifndef WINDOZE_HELPER_GROUP -#define WINDOZE_HELPER_GROUP - -/* -* Name : windoze_helper definitions -* Author : Chris Koeritz -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! @file windoze_helper.h Aids in achievement of platform independence. -/*! @file windoze_helper.h - These definitions, inclusions and types are aimed at allowing our source - code to remain independent of the underlying windowing and operating - systems. Specifically, windows puts a lot of burden on the developer, - and this file exists to hide all that malarkey. -*/ - -// gnarly headers that are needed for certain types of compilation... - -//unix headers not needed in here for new purpose of file. -#ifdef __UNIX__ - #include -#endif -#ifndef NO_XWINDOWS - #ifdef __XWINDOWS__ - #include - #include - #include - #include - #endif -#endif -#ifdef __WIN32__ - #ifndef STRICT - #define STRICT - #endif - // windows headers... - #define _WINSOCKAPI_ // make windows.h happy about winsock. - #ifndef _AFXDLL - // include ms-windows headers only if we're not doing mfc; mfc has its own - // special way of including the headers. - #include - #else - #include - #include - #endif - // winsock support... - #undef FD_SETSIZE - #define FD_SETSIZE 1000 - // if you don't set this, you can only select on a default of 64 sockets. - #include -#endif - -// forward. -//class version; - -// wrapper classes defined in the sequel... -// -// application_instance -- this program's application object; used mainly -// in ms-windows. -// -// window_handle -- a wrapper for the root of all objects that can be -// referred to in the relevant windowing system. - -#include - -#ifdef __UNIX__ - // the application_instance class is implemented very simply for unix; - // it's a stand-in since unix apps don't refer to an instance handle like - // ms-windows does. - typedef void *application_instance; - - // some definitions to quiet complaints from win32-based code. - #ifndef LONGINT_SIZE - #if defined(__alpha) || (defined(__HOS_AIX__) && defined(_LP64)) \ - || defined(__sparcv9) || defined(__LP64__) - #define LONGINT_SIZE 8 - #else - #define LONGINT_SIZE 4 - #endif - #endif - #if (LONGINT_SIZE == 4) - // be compatible with linux's definitions on this subject. - typedef unsigned long DWORD; - #else - typedef unsigned int DWORD; - #endif - - typedef void *HANDLE; - - //temp; these just mimic function prototypes coming from non-portable code. - typedef void *ATOM; - typedef void *BITMAPINFO; - typedef void *HBITMAP; - typedef void *HDC; - typedef void *RGBQUAD; - #define LoadBitmap(a, b) (a) - #define __stdcall - #define afx_msg - - // windoze_helper definitions for the X windowing system. - #ifndef NO_XWINDOWS // protects regions with similar names. - #ifdef __XWINDOWS__ - typedef Widget window_handle; - //!< main way to access a windowing system object. should be a pointer. - - typedef Colormap window_colormap; - //!< the windowing system representation of a pallette or colormap. - - typedef XColor window_color; - //!< the system representation of color. - - typedef XmString window_string; - //!< a special windowing system dependent kind of character string. - -//is that really fixed? - const int MAXIMUM_COLOR_INTENSITY = 65535; - // largest valid value a color component (R, G or B) can have. - #else - // no x-windows. - typedef void *window_handle; - #endif - #endif -#endif - -#ifdef __WIN32__ - typedef HINSTANCE application_instance; - // our moniker for an application's guts. - - typedef HWND window_handle; - // our alias for window handles in win32. -#endif - -namespace application { - -// istring module_name(const void *module_handle = NIL); - //!< returns the name of the module for the "module_handle" where supported. - /*!< if the handle is NIL, then the program name is returned. */ - - -// u_int system_error(); - //!< gets the most recent system error reported on this thread. - -// istring system_error_text(u_int error_to_show); - //!< returns the OS's string form of the "error_to_show". - /*!< this often comes from the value reported by system_error(). */ - -// istring null_device(); - //!< returns the name of the system's NULL device. - /*!< this device is a black hole where output can be sent, never to be - seen again. this is /dev/null on unix and null: on win32. */ - -// timeval fill_timeval_ms(int milliseconds); - //!< returns a timeval system object that represents the "milliseconds". - /*!< if "milliseconds" is zero, then the returned timeval will - represent zero time passing (rather than infinite duration as some - functions assume). */ - - // this only really helps for win32. - extern application_instance _i_handle; - //!< dll_root.cpp defines this for all dlls. - #define DEFINE_INSTANCE_HANDLE application_instance application::_i_handle = 0 - //!< some applications may need this to use rc_string and others. - /*!< if this macro is used, then the code is impervious to future - changes in how the instance handle works. */ - #define SET_INSTANCE_HANDLE(value) application::_i_handle = value - //!< use this to change the instance handle for this dll or exe. - /*!< note that this should be done only once and by the main thread. */ - #define GET_INSTANCE_HANDLE() application::_i_handle - //!< a handy macro that frees one from knowing the name of the handle. - -//////////////////////////////////////////////////////////////////////////// - - // Unix and Linux specific items are included here. - #ifdef __UNIX__ -//// istring get_cmdline_from_proc(); - //!< returns the command line that /proc has recorded for our process. - -// char *itoa(int to_convert, char *buffer, int radix); - //!< turns the integer "to_convert" into a string stored in the "buffer". - /*!< this is needed on linux since there doesn't seem to be a builtin - version of it. the buffer must be long enough to hold the number's - textual representation! the buffer's length must also account for the - chosen "radix" (the base for the number system, where 10 is decimal, - 16 is hexadecimal, etc). */ - - #define RGB(r, g, b) (b + (g << 8) + (r << 16)) - //!< no idea if that's even approximately right. - #endif - - // ms-windows of more modern types, i.e. win32. - #ifdef __WIN32__ - -// bool event_poll(MSG &message); - //!< tries to process one win32 event and retrieve the "message" from it. - /*!< this is a very general poll and will retrieve any message that's - available for the current thread. the message is actually processed - here also, by calling translate and dispatch. the returned structure - is mainly interesting for knowing what was done. */ - -//hmmm: is there an equivalent to this for unix? -// bool is_address_valid(const void *address, int size_expected, -// bool writable = false); - //!< checks that the address specified is a valid pointer. - /*! also tests that the address's allocated space has the - "size_expected". if "writable" is true, then the pointer is - checked for being writable as well as readable. */ - - #define BROADCAST_HANDLE HWND_BROADCAST - - enum known_operating_systems { - WIN_95, WIN_NT, WIN_2K, WIN_XP, WIN_SRV2K3, WIN_VISTA, WIN_SRV2K8, - UNKNOWN_OS - }; - const char *opsystem_name(known_operating_systems which); - //!< returns the textual form of the known_operating_systems enum. - - known_operating_systems determine_OS(); - //!< returns the operating system that seems to be running currently. - /*!< note that WIN_95 also covers windows 98 and windows ME. */ - -// istring rc_string(u_int id, application_instance instance); - //!< acquires a string from a particular "instance". - /*!< the "id" is the resource identifier for the desired string in - that "instance". */ - -// istring rc_string(u_int id); - //!< simplified from above function by not needing an "instance". - /*!< the "current" dynamic library or executable's resources are sought - for the "id". */ - -// void show_wait_cursor(); - //!< changes the cursor to the "wait" look, which is usually an hourglass. -// void show_normal_cursor(); - //!< changes the cursor to "normal", which is usually a pointing arrow. - - #endif // win32. - -} // namespace. - -#endif // outer guard. - diff --git a/core/library/basis/array.h b/core/library/basis/array.h deleted file mode 100644 index afd703e6..00000000 --- a/core/library/basis/array.h +++ /dev/null @@ -1,871 +0,0 @@ -#ifndef ARRAY_CLASS -#define ARRAY_CLASS - -/*****************************************************************************\ -* * -* Name : array * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "common_outcomes.h" -#include "definitions.h" -#include "enhance_cpp.h" -#include "functions.h" -#include "guards.h" -#include "outcome.h" - -#define DEBUG_ARRAY - // uncomment for the noisier debugging version. - -namespace basis { - -//! Represents a sequential, ordered, contiguous collection of objects. -/*! - This object manages a contiguous array of memory to hold the objects it - contains. The objects to be stored must have a constructor with zero - parameters, since the objects are stored in a C-style array (and array - constructors cannot be given arguments to be passed to the objects). - The objects must also either be flat (containing no pointers) or have an - assignment operator (=) that correctly copies the deep contents. - This class also provides an exponential growth mode for memory to reduce - thrashing; this allows the size pre-allocated to double every time a new - allocation is required during a resize. This causes the allocation to grow - very swiftly, speeding up usage of frequently growing arrays, but this may - not be desired for every array. - terms used here... - blank array: a array with some number of elements, but where those - elements are objects that have been constructed using their default - parameterless constructor. - empty array: a array of zero elements. -*/ - -template -class array : public virtual root_object -{ -public: - //! the flags specify how the array treats its contents and its length. - enum specialc_flags { - NO_SPECIAL_MODES = 0x0, //!< do nothing extra; only valid by itself. - SIMPLE_COPY = 0x1, //!< the contents can be memcpy'd and are not deep. - EXPONENTIAL_GROWTH = 0x2, //!< length is doubled when reallocation happens. - EXPONE = EXPONENTIAL_GROWTH, //!< synonym for EXPONENTIAL_GROWTH. - FLUSH_INVISIBLE = 0x4 //!< blanks out allocated but inaccessible elements. - }; - - DEFINE_CLASS_NAME("array"); - - enum how_to_copy { NEW_AT_END, NEW_AT_BEGINNING, DONT_COPY }; - //!< An enumeration of ways to shift existing contents in an array. - /*!< In the context of dynamically resizing an array of data, a range of - new elements can be added (or removed for that matter) at the end of the - existing space (NEW_AT_END), at the beginning of the existing space - (NEW_AT_BEGINNING), or we might not care about the existing data when - we perform the resize (DONT_COPY). These methods impose a particular - shift on the existing contents if we actually already have enough space - for the new contents. If there is space before the part of the array - that's in use and we want NEW_AT_END, then the existing contents are - jammed up into the front end of the array. */ - - array(int number = 0, const contents *init = NIL, - int flags = EXPONENTIAL_GROWTH | FLUSH_INVISIBLE); - //!< Constructs an array with room for "number" objects. - /*!< The initial contents are copied from "init" unless NIL is passed in - instead. If "init" is not NIL, then it must point to an array of objects - that contains at least "number". The "flags" are a value based on the - special flags being added bit-wise. If "flags" contains SIMPLE_COPY, then - memmove() is used rather than using the C++ object's assignment operator. - Note that SIMPLE_COPY will NOT work if the templated object has a regular - constructor or assignment operator, since those methods will not be - called on copying. If the "flags" contain EXPONENTIAL_GROWTH, then the - true allocation size will be doubled every time a new allocation is - required. when the FLUSH_INVISIBLE flag is included, then the array - elements that go out of scope are returned to the state provided by - the content's default constructor. this ensures that if they ever come - back into scope, they do not yet have any contents. further, if the - elements had any deep contents, those resources should be released. */ - - array(const array ©_from); - //!< copies the contents & sizing information from "copy_from". - - virtual ~array(); //!< destroys the memory allocated for the objects. - - void reset(int number = 0, const contents *initial_contents = NIL); - //!< Resizes this array and sets the contents from an array of contents. - /*< If "initial_contents" is not NIL, then it must contain an array of - "contents" with at least "number" objects in it. If it is NIL, then - the size of the array is changed but the contents are not. note that - any pre-existing elements that were previously out of range might still - have their prior contents; the newly available elements are not necessarily - "blank". thus, before using them, ensure you store appropriate elements - in those positions. */ - - array &operator = (const array ©_from); - //!< Copies the array in "copy_from" into this. - - int length() const { return c_active_length; } - //!< Returns the current reported length of the allocated C array. - - int last() const { return c_active_length - 1; } - //!< Returns the last valid element in the array. - - int flags() const { return c_flags; } - //!< Provides the raw flags value, without interpreting what it means. - - bool exponential() const { return c_flags & EXPONENTIAL_GROWTH; } - //!< Returns true if this allocator will grow exponentially on resize. - - bool simple() const { return c_flags & SIMPLE_COPY; } - //!< Reports whether the templated object is a simple type or not. - - const contents &get(int index) const; - //!< Accesses individual objects stored in "this" at the "index" position. - /*!< If the index is out of range, then a bogus reference (to internally - held garbage) is returned. */ - contents &use(int index); - //!< A non-constant version of get(); the returned object can be modified. - - const contents &operator [] (int index) const { return get(index); } - //!< Synonym for get that provides the expected array indexing syntax. - contents &operator [] (int index) { return use(index); } - //!< Synonym for use that provides the expected array indexing syntax. - - outcome put(int index, const contents &to_put); - //!< Stores an object at the index "index" in the array. - /*!< The outcome is "OUT_OF_RANGE" if the index does not exist. */ - - array concatenation(const array &to_concatenate) const; - //!< Returns the concatenation of "this" and the array "to_concatenate". - array concatenation(const contents &to_concatenate) const; - //!< Returns the concatenation of "this" and the object "to_concatenate". - - array &concatenate(const array &to_concatenate); - //!< Appends the array "to_concatenate" onto "this" and returns "this". - array &concatenate(const contents &to_concatenate); - //!< Appends the object "to_concatenate" onto "this" and returns "this". - array &concatenate(const contents *to_concatenate, int length); - //!< Concatenates a C-array "to_concatenate" onto "this" and returns "this". - /*!< There must be at least "length" elements in "to_concatenate". */ - - array operator + (const array &to_cat) const - { return concatenation(to_cat); } - //!< Synonym for concatenation. - array operator + (const contents &to_concatenate) const - { return concatenation(to_concatenate); } - //!< Synonym for concatenation. - array &operator += (const array &to_concatenate) - { return concatenate(to_concatenate); } - //!< Synonym for concatenate that modifies "this". - array &operator += (const contents &to_concatenate) - { return concatenate(to_concatenate); } - //!< Synonym for concatenate that modifies "this". - - const contents *observe() const { return c_offset; } - //!< Returns a pointer to the underlying C array of data. - /*!< The array contains "length()" number of objects in it. BE CAREFUL. - This version is a constant peek at that pointer. */ - contents *access() { return c_offset; } - //!< A non-constant access of the underlying C-array. BE REALLY CAREFUL. - - void swap_contents(array &other); - //!< Exchanges the contents of "this" and "other". - /*!< No validation is performed but this should always succeed given - arrays constructed properly. */ - - void snarf(array &new_contents); - //!< Drops "this" array's contents into the dustbin and uses "new_contents". - /*!< Afterwards, "new_contents" is an empty array and what used to be - stored there is now in "this" instead. */ - - array subarray(int start, int end) const; - //!< Returns the array segment between the indices "start" and "end". - /*!< This is all characters from "start" to "end" inclusively, as long as - those values are valid for this array. Even then, an intelligent default - is usually assumed if the indices are out of range. */ - - outcome insert(int index, int new_indices); - //!< Adds "new_indices" new positions for objects into the array at "index". - - outcome overwrite(int index, const array &write_with, int count = -1); - //!< Stores the array "write_with" into the current array at the "index". - /*!< The current contents are overwritten with "write_with". If the - index is invalid, then OUT_OF_RANGE is returned. If the "write_with" - array cannot fit due to the boundaries of "this" array, then only the - portion that can fit is used. If "count" is negative, the whole - "write_with" array is used; otherwise, only "count" elements are used. */ - - outcome stuff(int length, contents *to_stuff) const; - //!< Copies at most "length" elements from this into the array "to_stuff". - /*!< This call will fail disastrously if "length" is larger than the - array "to_stuff"'s allocated length. */ - - outcome resize(int new_size, how_to_copy way = NEW_AT_END); - //!< Changes the size of the C array to "new_size". - /*!< If "way" is NEW_AT_END and the array grows, then new space is added - at the end and the prefix of the array is the same as the old array. If - the "way" is NEW_AT_END, but the array shrinks, then the new array is a - prefix of the old array. If "way" is NEW_AT_BEGINNING and the array - grows, then the suffix of the array is the same as the old one and the - space is added at the beginning. if the "way" is NEW_AT_BEGINNING but the - array shrinks, then the new array is a suffix of the old array. if "way" - is DONT_COPY, then the old contents are not copied. keep in mind that - any newly visible elements can be in an arbitrary state and will not - necessarily be freshly constructed. */ - - outcome zap(int start, int end); - //!< Deletes from "this" the objects inclusively between "start" and "end". - /*!< C-array conventions are used (0 through length()-1 are valid if - length() > 0). If either index is out of range, then a default is - assumed. */ - - outcome shrink(); - //!< Cuts loose any allocated space that is beyond the real length. - - outcome retrain(int new_size, const contents *to_copy); - //!< Resizes the C array and stuffs it with the contents in "to_copy". - - enum shift_directions { TO_LEFT, TO_RIGHT }; - //!< All the real contents can be shifted to either the left or right. - - void shift_data(shift_directions where); - //!< The valid portion of the array is moved to the left or right. - /*!< This means that the unused space (dictated by the offset where the - data starts) will be adjusted. This may involve copying the data. */ - - // These are gritty internal information methods and should not be used - // except by appropriately careful code. - int internal_real_length() const { return c_real_length; } - //!< Gritty Internal: the real allocated length. - int internal_offset() const { return int(c_offset - c_mem_block); } - //!< Gritty Internal: the offset from real start to stored data. - const contents *internal_block_start() const { return c_mem_block; } - //!< Gritty Internal: constant peek at the real allocated pointer. - contents *internal_block_start() { return c_mem_block; } - //!< Gritty Internal: the real allocated pointer made accessible. - contents * const *internal_offset_mem() const { return &c_offset; } - //!< Gritty Internal: the start of the actual stored data. - -private: - int c_active_length; //!< the number of objects reported to be in the array. - int c_real_length; // the real number of objects that can be stored. - contents *c_mem_block; //!< a pointer to the objects held for this array. - contents *c_offset; //!< the beginning of the useful part of the memory block. - int c_flags; //!< records the special characteristics of this array. - - outcome allocator_reset(int initial_elements, int blocking); - //!< Allocates space for the "initial_elements" plus the "blocking" factor. - /*!< This resets the C-array to have "initial_elements" indices, plus an - extra pre-allocation of "blocking" that leaves room for expansion. Any - prior contents are whacked. */ -}; - -////////////// - -//! A simple object that wraps a templated array of ints. -class int_array : public array -{ -public: - int_array(int number = 0, const int *initial_contents = 0) - : root_object(), - array(number, initial_contents, SIMPLE_COPY | EXPONE) {} - //!< Constructs an array of "number" integers. - /*!< creates a list of ints based on an initial "number" of entries and - some "initial_contents", which should be a regular C array of ints - with at least as many entries as "number". */ -}; - -////////////// - -//! An array of double floating point numbers. -class double_array : public array -{ -public: - double_array(int len = 0, double *data = NIL) - : root_object(), - array(len, data, SIMPLE_COPY | EXPONE) {} - double_array(const array &to_copy) : array(to_copy) {} -}; - -////////////// - -// implementation code, much longer methods below... - -// GOALS: -// -// 1) provide a slightly smarter allocation method for C arrays and other -// contiguous-storage container classes with better speed and reduced memory -// fragmentation through pre-allocation. this can reduce memory thrashing -// when doing appends and inserts that can be granted with previously -// allocated, but unused, space. -// 2) clean-up bounds failure cases in functions that return a reference by -// always having at least one bogus element in the array for returns. this -// really just requires that we never allow our hidden real length of the -// array to be zero. - -template -array::array(int num, const contents *init, int flags) -: root_object(), c_active_length(0), c_real_length(0), c_mem_block(NIL), c_offset(NIL), c_flags(flags) -{ - if (c_flags > 7) { -#ifdef DEBUG_ARRAY - throw "error: array::constructor: error in parameters! still passing a block size?"; -#endif - c_flags = EXPONE | FLUSH_INVISIBLE; - // drop simple copy, since the caller doesn't know what they're doing. - } - - allocator_reset(num, 1); // get some space. - retrain(num, init); // plug in their contents. -} - -template -array::array(const array &cf) -: root_object(), c_active_length(0), c_real_length(0), c_mem_block(NIL), c_offset(NIL), c_flags(cf.c_flags) -{ - allocator_reset(cf.c_active_length, 1); // get some space. - operator = (cf); // assignment operator does the rest. -} - -template -array::~array() -{ - c_offset = NIL; - if (c_mem_block) delete [] c_mem_block; - c_mem_block = NIL; - c_active_length = 0; - c_real_length = 0; -} - -template -void array::reset(int num, const contents *init) -{ retrain(num, init); } - -template -array &array::operator =(const array &cf) -{ - if (this == &cf) return *this; - c_flags = cf.c_flags; // copy the flags coming in from the other object. - // prepare the array for retraining... - c_offset = c_mem_block; // slide the offset back to the start. - c_active_length = 0; // the length gets reset also. - retrain(cf.c_active_length, cf.observe()); - return *this; -} - -template -contents &array::use(int index) -{ - bounds_return(index, 0, this->last(), bogonic()); - return this->access()[index]; -} - -template -const contents &array::get(int index) const -{ - bounds_return(index, 0, this->last(), bogonic()); - return this->observe()[index]; -} - -template -array &array::concatenate(const array &s1) -{ - // check whether there's anything to concatenate. - if (!s1.length()) return *this; - if (this == &s1) { - // make sure they don't concatenate this array to itself. - return concatenate(array(*this)); - } - int old_len = this->length(); - resize(this->length() + s1.length(), NEW_AT_END); - overwrite(old_len, s1); - return *this; -} - -template -array &array::concatenate(const contents &to_concatenate) -{ - resize(this->length() + 1, NEW_AT_END); - if (!this->simple()) - this->access()[this->last()] = to_concatenate; - else - memcpy(&(this->access()[this->last()]), &to_concatenate, sizeof(contents)); - return *this; -} - -template -array &array::concatenate(const contents *to_concatenate, - int length) -{ - if (!length) return *this; // nothing to do. - const int old_len = this->length(); - resize(this->length() + length, NEW_AT_END); - if (!this->simple()) - for (int i = 0; i < length; i++) - this->access()[old_len + i] = to_concatenate[i]; - else - memcpy(&(this->access()[old_len]), to_concatenate, - length * sizeof(contents)); - return *this; -} - -template -array array::concatenation(const array &s1) const -{ - // tailor the return array to the new size needed. - array to_return(this->length() + s1.length(), NIL, s1.c_flags); - to_return.overwrite(0, *this); // put the first part into the new array. - to_return.overwrite(this->length(), s1); // add the second segment. - return to_return; -} - -template -array array::concatenation(const contents &s1) const -{ - array to_return(this->length() + 1, NIL, c_flags); - to_return.overwrite(0, *this); - if (!this->simple()) - to_return.access()[to_return.last()] = s1; - else - memcpy(&(to_return.access()[to_return.last()]), &s1, sizeof(contents)); - return to_return; -} - -template -array array::subarray(int start, int end) const -{ - bounds_return(start, 0, this->last(), array(0, NIL, c_flags)); - bounds_return(end, 0, this->last(), array(0, NIL, c_flags)); - if (start > end) return array(0, NIL, c_flags); - return array(end - start + 1, &(this->observe()[start]), c_flags); -} - -template -void array::swap_contents(array &other) -{ - if (this == &other) return; // already swapped then, i suppose. - swap_values(this->c_active_length, other.c_active_length); - swap_values(this->c_real_length, other.c_real_length); - swap_values(this->c_offset, other.c_offset); - swap_values(this->c_mem_block, other.c_mem_block); - swap_values(this->c_flags, other.c_flags); -} - -template -outcome array::shrink() -{ - if (!c_mem_block) return common::OUT_OF_MEMORY; - if (c_active_length == c_real_length) return common::OKAY; // already just right. - array new_holder(*this); - // create a copy of this object that is just the size needed. - swap_contents(new_holder); - // swap this object with the copy, leaving the enlarged version behind - // for destruction. - return common::OKAY; -} - -template -outcome array::stuff(int lengthx, contents *to_stuff) const -{ - if (!lengthx || !this->length()) return common::OKAY; - int copy_len = minimum(lengthx, this->length()); - if (!this->simple()) { - for (int i = 0; i < copy_len; i++) - to_stuff[i] = this->observe()[i]; - } else { - memcpy(to_stuff, this->observe(), copy_len * sizeof(contents)); - } - return common::OKAY; -} - -template -outcome array::overwrite(int position, - const array &write_with, int count) -{ - if (!count) return common::OKAY; - if ( (this == &write_with) || !this->length() || !write_with.length()) - return common::BAD_INPUT; - bounds_return(position, 0, this->last(), common::OUT_OF_RANGE); - if ( negative(count) || (count > write_with.length()) ) - count = write_with.length(); - if (position > this->length() - count) - count = this->length() - position; - if (!this->simple()) { - for (int i = position; i < position + count; i++) - this->access()[i] = write_with.observe()[i - position]; - } else { - memcpy(&(this->access()[position]), write_with.observe(), - count * sizeof(contents)); - } - return common::OKAY; -} - -template -outcome array::allocator_reset(int initial, int blocking) -{ -// FUNCDEF("allocator_reset") - if (blocking < 1) { -#ifdef DEBUG_ARRAY - throw "error: array::allocator_reset: has bad block size"; -#endif - blocking = 1; - } - if (initial < 0) initial = 0; // no antimatter arrays. - if (c_mem_block) { - // remove old contents. - delete [] c_mem_block; - c_mem_block = NIL; - c_offset = NIL; - } - c_active_length = initial; // reset the length to the reporting size. - c_real_length = initial + blocking; // compute the real length. - if (c_real_length) { - c_mem_block = new contents[c_real_length]; - if (!c_mem_block) { - // this is an odd situation; memory allocation didn't blow out an - // exception, but the memory block is empty. let's consider that - // a fatal error; we can't issue empty objects. - throw common::OUT_OF_MEMORY; - } - c_offset = c_mem_block; // reset offset to start of array. - } - return common::OKAY; -} - -template -void array::shift_data(shift_directions where) -{ - if (where == TO_LEFT) { - // we want to end up with the data jammed up against the left edge. thus - // we need the offset to be zero bytes from start. - if (c_offset == c_mem_block) - return; // offset already at start, we're done. - // well, we need to move the data. - if (simple()) { - memmove(c_mem_block, c_offset, c_active_length * sizeof(contents)); - } else { - for (contents *ptr = c_offset; ptr < c_offset + c_active_length; ptr++) - c_mem_block[ptr - c_offset] = *ptr; - } - c_offset = c_mem_block; // we've ensured that this is correct. - if (c_flags & FLUSH_INVISIBLE) { - // we need to clean up what might have had contents previously. -// for (contents *p = c_mem_block + c_active_length; p < c_mem_block + c_real_length; p++) -// *p = contents(); - } - } else { - // we want to move the data to the right, so the offset should be the - // difference between the real length and the length. - if (c_offset == c_mem_block + c_real_length - c_active_length) - return; // the offset is already the right size. - if (simple()) { - memmove(&c_mem_block[c_real_length - c_active_length], c_offset, c_active_length * sizeof(contents)); - } else { - for (int i = c_real_length - 1; i >= c_real_length - c_active_length; i--) - c_mem_block[i] = c_offset[i - c_real_length + c_active_length]; - } - c_offset = c_mem_block + c_real_length - c_active_length; // we've now ensured this. - if (c_flags & FLUSH_INVISIBLE) { - // we need to clean up the space on the left where old contents might be. -// for (contents *p = c_mem_block; p < c_offset; p++) -// *p = contents(); - } - } -} - -template -outcome array::resize(int new_size, how_to_copy way) -{ -/// FUNCDEF("resize"); - if (new_size < 0) new_size = 0; // we stifle this. - if (new_size == c_active_length) { - // nothing much to do. - return common::OKAY; - } - // okay, now we at least know that the sizes are different. we save all the - // old information about the array prior to this resizing. - contents *old_s = c_mem_block; // save the old contents... - const int old_len = c_active_length; // and length. - contents *old_off = c_offset; // and offset. - bool delete_old = false; // if true, old memory is whacked once it's copied. - -//hmmm: wasn't there a nice realization that we could bail out early in -// the case where the size is already suffcient? there seems to be -// an extraneous copy case here. -// also it would be nice to have better, more descriptive names for the -// variables here since they are not lending themselves to understanding -// the algorithm. - - // we check whether there's easily enough space in the array already. - // if not, then we have some more decisions to make. - if (c_real_length - (old_off - old_s) < new_size) { - // well, there's not enough space with the current space and offset. - if (c_real_length < new_size) { - // there's really not enough space overall, no fooling. we now will - // create a new block. - c_mem_block = NIL; // zero out the pointer so reset doesn't delete it. - delete_old = true; - int blocking = 1; - if (exponential()) blocking = new_size + 1; - outcome ret = allocator_reset(new_size, blocking); - if (ret != common::OKAY) { - // failure, but don't forget to whack the old glob. -#ifdef DEBUG_ARRAY - throw "error: array::resize: saw array reset failure"; -#endif - delete [] old_s; - return ret; - } - // fall out to the copying phase, now that we have some fresh memory. - } else { - // there is enough space if we shift some things around. - const int size_difference = new_size - c_active_length; - // we compute how much space has to be found in the array somewhere - // to support the new larger size. - if (way == DONT_COPY) { - // simplest case; just reset the offset appropriately so the new_size - // will fit. - c_offset = c_mem_block; - c_active_length = new_size; - } else if (way == NEW_AT_BEGINNING) { - // if the new space is at the beginning, there are two cases. either - // the new size can be accomodated by the current position of the - // data or the data must be shifted to the right. - if (c_offset - c_mem_block < size_difference) { - // we need to shift the data over to the right since the offset isn't - // big enough for the size increase. - shift_data(TO_RIGHT); // resets the offset appropriately. - } - // now we know that the amount of space prior to the real data - // is sufficient to hold what new space is needed. we just need to - // shift the offset back somewhat. - c_offset -= size_difference; - c_active_length = new_size; - } else { - // better only be three ways to do this; we're now assuming the new - // space should be at the end (NEW_AT_END). - // now that we're here, we know there will be enough space if we shift - // the block to the left, but we DO NEED to do this. if we didn't need - // to shift the data, then we would find that: - // c_real_length - old_off >= new_size - // which is disallowed by the guardian conditional around this area. - shift_data(TO_LEFT); // resets the offset for us. - c_active_length = new_size; - } - // we have ensured that we had enough space and we have already shifted - // the data around appropriately. we no longer need to enter the next - // block where we would copy data around if we had to. it has become - // primarily for cases where either we have to copy data because we - // have new storage to fill or where we are shrinking the array. - return common::OKAY; - } - } - - // the blob of code below is offset invariant. by the time we reach here, - // the array should be big enough and the offset should be okay. - c_active_length = new_size; // set length to the new reporting size. - if (way != DONT_COPY) { - int where = 0; // offset for storing into new array. - bool do_copy = false; // if true, then we need to move the data around. - contents *loopc_offset_old = old_off; // offset into original object. - // we should only have to copy the memory in one other case besides our - // inhabiting new memory--when we are asked to resize with the new stuff - // at the beginning of the array. if the new space is at the end, we're - // already looking proper, but if the new stuff is at the beginning, we - // need to shift existing stuff downwards. - if (way == NEW_AT_BEGINNING) { - where = new_size - old_len; // move up existing junk. - if (where) do_copy = true; // do copy, since it's not immobile. - if (where < 0) { - // array shrank; we need to do the loop differently for starting - // from the beginning. we skip to the point in the array that our - // suffix needs to start at. - loopc_offset_old -= where; - where = 0; // reset where so we don't have negative offsets. - } - } - const int size_now = minimum(old_len, c_active_length); - if (delete_old || do_copy) { - contents *offset_in_new = c_offset + where; - contents *posn_in_old = loopc_offset_old; - if (simple()) { - // memmove should take care of intersections. - memmove(offset_in_new, posn_in_old, size_now * sizeof(contents)); - } else { - // we need to do the copies using the object's assignment operator. - if (new_size >= old_len) { - for (int i = size_now - 1; i >= 0; i--) - offset_in_new[i] = posn_in_old[i]; - } else { - for (int i = 0; i < size_now; i++) - offset_in_new[i] = posn_in_old[i]; - } - } - - // we only want to flush the obscured elements when we aren't already - // inhabiting new space. - if ( (c_flags & FLUSH_INVISIBLE) && !delete_old) { - // clear out the space that just went out of scope. we only do this - // for the flushing mode and when we aren't starting from a fresh - // pointer (i.e., delete_old isn't true). - if (new_size < old_len) { -// for (contents *p = posn_in_old; p < offset_in_new; p++) -// *p = contents(); - } - } - } - } - if (delete_old) delete [] old_s; - return common::OKAY; -} - -template -outcome array::retrain(int new_len, const contents *to_set) -{ -/// FUNCDEF("retrain") - if (new_len < 0) new_len = 0; // stifle that bad length. -#ifdef DEBUG_ARRAY - if (to_set && (c_mem_block >= to_set) && (c_mem_block < to_set + new_len) ) { - throw "error: array::retrain: ranges overlap in retrain!"; - } -#endif - outcome ret = resize(new_len, DONT_COPY); - if (ret != common::OKAY) return ret; -#ifdef DEBUG_ARRAY - if (new_len != c_active_length) { - throw "error: array resize set the wrong length"; - } -#endif - if (to_set) { - if (simple()) - memcpy(c_offset, to_set, c_active_length * sizeof(contents)); - else - for (int i = 0; i < c_active_length; i++) - c_offset[i] = to_set[i]; - } else { - if (c_flags & FLUSH_INVISIBLE) { - // no contents provided, so stuff the space with blanks. -// for (int i = 0; i < c_active_length; i++) c_offset[i] = contents(); - } - } - if (c_flags & FLUSH_INVISIBLE) { -// for (contents *ptr = c_mem_block; ptr < c_offset; ptr++) -// *ptr = contents(); -// for (contents *ptr = c_offset + c_active_length; ptr < c_mem_block + c_real_length; ptr++) -// *ptr = contents(); - } - return common::OKAY; -} - -template -outcome array::zap(int position1, int position2) -{ - if (position1 > position2) return common::OKAY; - bounds_return(position1, 0, c_active_length - 1, common::OUT_OF_RANGE); - bounds_return(position2, 0, c_active_length - 1, common::OUT_OF_RANGE); - if (!position1) { - // if they're whacking from the beginning, we just reset the offset. - c_offset += position2 + 1; - c_active_length -= position2 + 1; - return common::OKAY; - } - const int difference = position2 - position1 + 1; - // copy from just above position2 down into position1. - if (simple()) { - if (c_active_length - difference - position1 > 0) - memmove(&c_offset[position1], &c_offset[position1 + difference], - (c_active_length - difference - position1) * sizeof(contents)); - } else { - for (int i = position1; i < c_active_length - difference; i++) - c_offset[i] = c_offset[i + difference]; - } - - outcome ret = resize(c_active_length - difference, NEW_AT_END); - // chop down to new size. -#ifdef DEBUG_ARRAY - if (ret != common::OKAY) { - throw "error: array::zap: resize failure"; - return ret; - } -#endif - return ret; -} - -template -outcome array::insert(int position, int elem_to_add) -{ - if (position < 0) return common::OUT_OF_RANGE; - if (position > this->length()) - position = this->length(); - if (elem_to_add < 0) return common::OUT_OF_RANGE; - how_to_copy how = NEW_AT_END; - if (position == 0) how = NEW_AT_BEGINNING; - resize(this->length() + elem_to_add, how); - - // if the insert wasn't at the front, we have to copy stuff into the new - // locations. - if (how == NEW_AT_END) { - const contents simple_default_object = contents(); - if (!this->simple()) { - for (int i = this->last(); i >= position + elem_to_add; i--) - this->access()[i] = this->observe()[i - elem_to_add]; - for (int j = position; j < position + elem_to_add; j++) - this->access()[j] = simple_default_object; - } else { - memmove(&(this->access()[position + elem_to_add]), - &(this->observe()[position]), (this->length() - position - - elem_to_add) * sizeof(contents)); - for (int j = position; j < position + elem_to_add; j++) - memcpy(&this->access()[j], &simple_default_object, sizeof(contents)); - } - } - return common::OKAY; -} - -template -outcome array::put(int index, const contents &to_put) -{ - bounds_return(index, 0, this->last(), common::OUT_OF_RANGE); - if (!this->simple()) - this->access()[index] = to_put; - else - memcpy(&(this->access()[index]), &to_put, sizeof(contents)); - return common::OKAY; -} - -template -void array::snarf(array &new_contents) -{ - if (this == &new_contents) return; // no blasting own feet off. - reset(); // trash our current storage. - swap_contents(new_contents); -} - -/* -//! a simple wrapper of an array of char *, used by portable::break_line(). -class char_star_array : public array -{ -public: - char_star_array() : array(0, NIL, SIMPLE_COPY | EXPONE - | FLUSH_INVISIBLE) {} - ~char_star_array() { - // clean up all the memory we're holding. - for (int i = 0; i < length(); i++) { - delete [] (use(i)); - } - } -}; -*/ - -} //namespace - -#undef static_class_name - -#endif - diff --git a/core/library/basis/astring.cpp b/core/library/basis/astring.cpp deleted file mode 100644 index 55b9daf8..00000000 --- a/core/library/basis/astring.cpp +++ /dev/null @@ -1,1096 +0,0 @@ -/* -* Name : astring -* Author : Chris Koeritz -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "astring.h" -#include "definitions.h" -#include "functions.h" -#include "guards.h" - -#include -#include -#include - -#ifdef __WIN32__ - #undef strcasecmp - #undef strncasecmp - #define strcasecmp strcmpi - #define strncasecmp strnicmp -#endif - -//#define DEBUG_STRING - // uncomment for debugging version. - -#define no_increment - // macro just documents a blank parameter in the code. - -namespace basis { - -const int LONGEST_SPRINTF = 600; // the longest a simple sprintf can be here. - -const char CASE_DIFFERENCE = char('A' - 'a'); - // the measurement of the difference between upper and lower case. - -// this factor is used to bias dynamic sprintfs for cases where the length -// is specified, but the actual string is shorter than that length. -const int MAX_FIELD_FUDGE_FACTOR = 64; - -const abyte empty_char_star[] = { 0 }; - // used to initialize empty strings. - -////////////// - -bool astring_comparator(const astring &a, const astring &b) { return a.equal_to(b); } - -int calculate_proper_length(int repeat) { return negative(repeat)? 1 : repeat + 1; } - -////////////// - -astring::astring() -: c_character_manager(1, empty_char_star), - c_held_string((char * const *)c_character_manager.internal_offset_mem()) -{} - -astring::astring(const base_string &initial) -: c_character_manager(strlen(initial.observe()) + 1, (abyte *)initial.observe()), - c_held_string((char * const *)c_character_manager.internal_offset_mem()) -{} - -astring::astring(char initial, int repeat) -: c_character_manager(calculate_proper_length(repeat)) -{ - if (!initial) initial = ' '; // for nulls, we use spaces. - int new_size = c_character_manager.length() - 1; - memset(c_character_manager.access(), initial, new_size); - c_character_manager.put(new_size, '\0'); - c_held_string = (char * const *)c_character_manager.internal_offset_mem(); -} - -astring::astring(const astring &s1) -: base_string(), - c_character_manager(s1.c_character_manager), - c_held_string((char * const *)c_character_manager.internal_offset_mem()) -{ -} - -astring::astring(const char *initial) -: c_character_manager(calculate_proper_length(initial? int(strlen(initial)) : 0)) -{ - c_character_manager.put(0, '\0'); - if (!initial) return; // bail because there's no string to copy. - strcpy(access(), initial); - c_held_string = (char * const *)c_character_manager.internal_offset_mem(); -} - -astring::astring(special_flag flag, const char *initial, ...) -: c_character_manager(1, empty_char_star), - c_held_string((char * const *)c_character_manager.internal_offset_mem()) -{ - if (!initial) return; - if ( (flag != UNTERMINATED) && (flag != SPRINTF) ) { - operator = (astring(astring::SPRINTF, "unknown flag %d", flag)); - return; - } - - va_list args; - va_start(args, initial); - - if (flag == UNTERMINATED) { - // special process for grabbing a string that has no terminating nil. - int length = va_arg(args, int); // get the length of the string out. - c_character_manager.reset(length, (abyte *)initial); - c_character_manager += abyte(0); - va_end(args); - return; - } - - // only other flag currently supported is sprintf, so we do that... - base_sprintf(initial, args); - va_end(args); -} - -astring::~astring() { c_held_string = NIL; } - -const astring &astring::empty_string() { return bogonic(); } - -void astring::text_form(base_string &state_fill) const { state_fill.assign(*this); } - -int astring::length() const { return c_character_manager.length() - 1; } - -byte_array &astring::get_implementation() { return c_character_manager; } - -char *astring::access() { return (char *)c_character_manager.access(); } - -char astring::get(int index) const { return (char)c_character_manager.get(index); } - -const char *astring::observe() const -{ return (const char *)c_character_manager.observe(); } - -bool astring::equal_to(const equalizable &s2) const -{ - const astring *s2_cast = cast_or_throw(s2, *this); - return comparator(*s2_cast) == 0; -} - -bool astring::less_than(const orderable &s2) const -{ - const astring *s2_cast = dynamic_cast(&s2); - if (!s2_cast) throw "error: astring::<: unknown type"; - return comparator(*s2_cast) < 0; -} - -int astring::comparator(const astring &s2) const -{ return strcmp(observe(), s2.observe()); } - -bool astring::equal_to(const char *that) const -{ return strcmp(observe(), that) == 0; } - -bool astring::contains(const astring &to_find) const -{ return (find(to_find, 0) < 0) ? false : true; } - -astring &astring::operator += (const astring &s1) -{ insert(length(), s1); return *this; } - -void astring::shrink() -{ - astring copy_of_this(observe()); - c_character_manager.swap_contents(copy_of_this.c_character_manager); -} - -astring &astring::sprintf(const char *initial, ...) -{ - va_list args; - va_start(args, initial); - astring &to_return = base_sprintf(initial, args); - va_end(args); - return to_return; -} - -astring &astring::base_sprintf(const char *initial, va_list &args) -{ - reset(); - if (!initial) return *this; // skip null strings. - if (!initial[0]) return *this; // skip empty strings. - - // these accumulate parts of the sprintf format within the loop. - char flag_chars[23], width_chars[23], precision_chars[23], modifier_chars[23]; - - // thanks for the inspiration to k&r page 156. - for (const char *traverser = initial; *traverser; traverser++) { -#ifdef DEBUG_STRING - printf("index=%d, char=%c\n", traverser - initial, *traverser); -#endif - - if (*traverser != '%') { - // not a special character, so just drop it in. - *this += *traverser; - continue; - } - traverser++; // go to the next character. -#ifdef DEBUG_STRING - printf("index=%d, char=%c\n", traverser - initial, *traverser); -#endif - if (*traverser == '%') { - // capture the "%%" style format specifier. - *this += *traverser; - continue; - } - bool failure = false; - // becomes set to true if something didn't match in a necessary area. - - seek_flag(traverser, flag_chars, failure); - if (failure) { - *this += '%'; - *this += flag_chars; - continue; - } - seek_width(traverser, width_chars); - seek_precision(traverser, precision_chars); - seek_modifier(traverser, modifier_chars); - get_type_character(traverser, args, *this, flag_chars, - width_chars, precision_chars, modifier_chars); - } - return *this; -} - -void astring::seek_flag(const char *&traverser, char *flag_chars, bool &failure) -{ - flag_chars[0] = '\0'; - failure = false; - bool keep_going = true; - while (!failure && keep_going) { - switch (*traverser) { - case '-': case '+': case ' ': case '\011': case '#': - flag_chars[strlen(flag_chars) + 1] = '\0'; - flag_chars[strlen(flag_chars)] = *traverser++; - break; - default: - // we found a character that doesn't belong in the flags. - keep_going = false; - break; - } - } -#ifdef DEBUG_STRING - if (strlen(flag_chars)) printf("[flag=%s]\n", flag_chars); - else printf("no flags\n"); -#endif -} - -void astring::seek_width(const char *&traverser, char *width_chars) -{ - width_chars[0] = '\0'; - bool no_more_nums = false; - bool first_num = true; - while (!no_more_nums) { - char wideness[2] = { *traverser, '\0' }; - if (first_num && (wideness[0] == '0')) { - strcpy(width_chars, wideness); - traverser++; - } else if (first_num && (wideness[0] == '*') ) { - strcpy(width_chars, wideness); - traverser++; - no_more_nums = true; - } else if ( (wideness[0] <= '9') && (wideness[0] >= '0') ) { - // a failure? - strcat(width_chars, wideness); - traverser++; - } else no_more_nums = true; - first_num = false; - } -#ifdef DEBUG_STRING - if (strlen(width_chars)) printf("[width=%s]\n", width_chars); - else printf("no widths\n"); -#endif -} - -void astring::seek_precision(const char *&traverser, char *precision_chars) -{ - precision_chars[0] = '\0'; - if (*traverser != '.') return; - strcpy(precision_chars, "."); - traverser++; - bool no_more_nums = false; - bool first_num = true; - while (!no_more_nums) { - char preciseness[2] = { *traverser, '\0' }; - if (first_num && (preciseness[0] == '0')) { - strcat(precision_chars, preciseness); - traverser++; - no_more_nums = true; - } else if (first_num && (preciseness[0] == '*') ) { - strcat(precision_chars, preciseness); - traverser++; - no_more_nums = true; - } else if ( (preciseness[0] <= '9') && (preciseness[0] >= '0') ) { - strcat(precision_chars, preciseness); - traverser++; - } else no_more_nums = true; - first_num = false; - } -#ifdef DEBUG_STRING - if (strlen(precision_chars)) printf("[precis=%s]\n", precision_chars); - else printf("no precision\n"); -#endif -} - -void astring::seek_modifier(const char *&traverser, char *modifier_chars) -{ - modifier_chars[0] = '\0'; - switch (*traverser) { - case 'F': case 'N': case 'h': case 'l': case 'L': { - modifier_chars[strlen(modifier_chars) + 1] = '\0'; - modifier_chars[strlen(modifier_chars)] = *traverser++; - break; - } - } -#ifdef DEBUG_STRING - if (strlen(modifier_chars)) printf("[mod=%s]\n", modifier_chars); - else printf("no modifiers\n"); -#endif -} - -void astring::get_type_character(const char * &traverser, va_list &args, - astring &output_string, const char *flag_chars, const char *width_chars, - const char *precision_chars, const char *modifier_chars) -{ - char formatting[120]; - strcpy(formatting, "%"); - strcat(formatting, flag_chars); - strcat(formatting, width_chars); - strcat(formatting, precision_chars); - strcat(formatting, modifier_chars); - char tmposh[2] = { *traverser, '\0' }; - strcat(formatting, tmposh); -#ifdef DEBUG_STRING - printf("format: %s\n", formatting); -#endif - - enum argument_size { bits_8, bits_16, bits_32, bits_64, bits_80 }; - bool ints_are_32_bits; -#ifdef __WIN32__ - ints_are_32_bits = true; -#elif defined(__OS2__) - ints_are_32_bits = true; -#elif defined(__MSDOS__) - ints_are_32_bits = false; -#elif defined(__WIN32__) - ints_are_32_bits = false; -#else - ints_are_32_bits = true; -#endif - argument_size next_argument; - bool use_dynamic_sprintf = false; // for dynamic printing of strings only. - // get the type character first and ensure it's valid. - switch (*traverser) { - case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': - next_argument = bits_16; - if (ints_are_32_bits) next_argument = bits_32; - break; - case 'f': case 'e': case 'g': case 'E': case 'G': - next_argument = bits_64; - break; - case 'c': - next_argument = bits_8; - break; - case 's': - next_argument = bits_32; - use_dynamic_sprintf = true; - break; - case 'n': - next_argument = bits_32; //????? - break; - case 'p': - next_argument = bits_32; //???? - break; - default: - // this is an error; the character is not recognized, so spew out - // any characters accumulated so far as just themselves. -#ifdef DEBUG_STRING - printf("failure in type char: %c\n", *traverser); -#endif - output_string += formatting; - return; - } -/* hmmm: not supported yet. - if (width_chars && (width_chars[0] == '*')) { - } - if (precision_chars && (precision_chars[0] == '*')) { - } -*/ - if (strlen(modifier_chars)) { - switch (modifier_chars[0]) { - case 'N': // near pointer. - next_argument = bits_16; - if (ints_are_32_bits) next_argument = bits_32; - break; - case 'F': // far pointer. - next_argument = bits_32; - break; - case 'h': // short int. - next_argument = bits_16; - break; - case 'l': // long. - next_argument = bits_32; - break; - case 'L': // long double; - next_argument = bits_80; - break; - default: - // a failure has occurred because the modifier character is not - // one of the recognized values. everything is just spewed out. -#ifdef DEBUG_STRING - printf("failure in modifier: %s\n", modifier_chars); -#endif - output_string += formatting; - return; - } - } - // action time: the output string is given a tasty value. - char temp[LONGEST_SPRINTF]; - char *temp2 = NIL; // for dynamic only. - switch (next_argument) { -//hmmm: this switch is where support would need to be added for having two -// arguments (for the '*' case). - case bits_8: case bits_16: - if (ints_are_32_bits) ::sprintf(temp, formatting, va_arg(args, long)); - else ::sprintf(temp, formatting, va_arg(args, int)); - break; - case bits_32: - if (use_dynamic_sprintf) { - // currently we only do dynamic sprintf for strings. - char *to_print = va_arg(args, char *); - // check if it's valid and if we really need to do it dynamically. - if (!to_print) { - // bogus string; put in a complaint. - use_dynamic_sprintf = false; - ::sprintf(temp, "{error:parm=NIL}"); - } else if (strlen(to_print) < LONGEST_SPRINTF - 2) { - // we're within our bounds, plus some safety room, so just do a - // regular sprintf. - use_dynamic_sprintf = false; - ::sprintf(temp, formatting, to_print); - } else { - // it's too long, so we definitely need to do it dynamically. - temp2 = new char[strlen(to_print) + MAX_FIELD_FUDGE_FACTOR]; - ::sprintf(temp2, formatting, to_print); - } - } else ::sprintf(temp, formatting, va_arg(args, void *)); - break; - case bits_64: - ::sprintf(temp, formatting, va_arg(args, double)); - break; - case bits_80: - ::sprintf(temp, formatting, va_arg(args, long double)); - break; - } - if (use_dynamic_sprintf) { - output_string += temp2; - delete [] temp2; - } else output_string += temp; -} - -//hmmm: de-redundify this function, which is identical to the constructor. -void astring::reset(special_flag flag, const char *initial, ...) -{ - reset(); // clear the string out. - if (!initial) return; - if ( (flag != UNTERMINATED) && (flag != SPRINTF) ) { - operator = (astring(astring::SPRINTF, "unknown flag %d", flag)); - return; - } - - va_list args; - va_start(args, initial); - - if (flag == UNTERMINATED) { - // special process for grabbing a string that has no terminating nil. - int length = va_arg(args, int); // get the length of the string out. - c_character_manager.reset(length, (abyte *)initial); - c_character_manager += abyte(0); - va_end(args); - return; - } - - // only other flag currently supported is sprintf, so we do that... - base_sprintf(initial, args); - va_end(args); -} - -void astring::pad(int len, char padding) -{ - if (length() >= len) return; - byte_array pad(len - length()); - memset(pad.access(), padding, pad.length()); - operator += (astring(UNTERMINATED, (char *)pad.observe(), pad.length())); -} - -void astring::trim(int len) -{ - if (length() <= len) return; - zap(len, end()); -} - -astring &astring::operator = (const astring &s1) -{ - if (this != &s1) - c_character_manager = s1.c_character_manager; - return *this; -} - -astring &astring::operator = (const char *s1) -{ - reset(); - *this += s1; - return *this; -} - -void astring::zap(int position1, int position2) -{ - bounds_return(position1, 0, end(), ); - bounds_return(position2, 0, end(), ); - c_character_manager.zap(position1, position2); -} - -void astring::to_lower() -{ - for (int i = 0; i < length(); i++) - if ( (get(i) >= 'A') && (get(i) <= 'Z') ) - c_character_manager.put(i, char(get(i) - CASE_DIFFERENCE)); -} - -void astring::to_upper() -{ - for (int i = 0; i < length(); i++) - if ( (get(i) >= 'a') && (get(i) <= 'z') ) - c_character_manager.put(i, char(get(i) + CASE_DIFFERENCE)); -} - -astring astring::lower() const -{ - astring to_return(*this); - to_return.to_lower(); - return to_return; -} - -astring astring::upper() const -{ - astring to_return(*this); - to_return.to_upper(); - return to_return; -} - -void astring::copy(char *array_to_stuff, int how_many) const -{ - if (!array_to_stuff) return; - array_to_stuff[0] = '\0'; - if ( (how_many <= 0) || (length() <= 0) ) return; - strncpy(array_to_stuff, observe(), minimum(how_many, int(length()))); - array_to_stuff[minimum(how_many, int(length()))] = '\0'; -} - -bool astring::iequals(const astring &that) const -{ return strcasecmp(observe(), that.observe()) == 0; } - -bool astring::iequals(const char *that) const -{ return strcasecmp(observe(), that) == 0; } - -int astring::ifind(char to_find, int position, bool reverse) const -{ return char_find(to_find, position, reverse, false); } - -int astring::find(char to_find, int position, bool reverse) const -{ return char_find(to_find, position, reverse, true); } - -int astring::find_any(const char *to_find, int position, bool reverse) const -{ return char_find_any(to_find, position, reverse, true); } - -int astring::ifind_any(const char *to_find, int position, bool reverse) const -{ return char_find_any(to_find, position, reverse, false); } - -int astring::find_non_match(const char *to_find, int position, - bool reverse) const -{ return char_find_any(to_find, position, reverse, false, true); } - -char simple_lower(char input) -{ - if ( (input <= 'Z') && (input >= 'A') ) return input - CASE_DIFFERENCE; - return input; -} - -int astring::char_find(char to_find, int position, bool reverse, - bool case_sense) const -{ - if (position < 0) return common::OUT_OF_RANGE; - if (position > end()) return common::OUT_OF_RANGE; - if (reverse) { - for (int i = position; i >= 0; i--) { - if (case_sense && (get(i) == to_find)) return i; - else if (simple_lower(get(i)) == simple_lower(to_find)) return i; - } - } else { - if (case_sense) { - const char *const pos = strchr(observe() + position, to_find); - if (pos) return int(pos - observe()); - } else { - for (int i = position; i < length(); i++) - if (simple_lower(get(i)) == simple_lower(to_find)) return i; - } - } - return common::NOT_FOUND; -} - -bool imatches_any(char to_check, const astring &list) -{ - for (int i = 0; i < list.length(); i++) - if (simple_lower(to_check) == simple_lower(list[i])) return true; - return false; -} - -bool matches_any(char to_check, const astring &list) -{ - for (int i = 0; i < list.length(); i++) - if (to_check == list[i]) return true; - return false; -} - -bool matches_none(char to_check, const astring &list) -{ - bool saw_match = false; - for (int i = 0; i < list.length(); i++) - if (to_check == list[i]) { - saw_match = true; - break; - } - return !saw_match; -} - -int astring::char_find_any(const astring &to_find, int position, bool reverse, - bool case_sense, bool invert_find) const -{ - if (position < 0) return common::OUT_OF_RANGE; - if (position > end()) return common::OUT_OF_RANGE; - if (reverse) { - for (int i = position; i >= 0; i--) { - if (!invert_find) { - if (case_sense && matches_any(get(i), to_find)) return i; - else if (imatches_any(get(i), to_find)) return i; - } else { -//printf("rev posn=%d char=%c", i, get(i)); - // case-sensitivity is not used for inverted finds. - if (matches_none(get(i), to_find)) return i; - } - } - } else { - for (int i = position; i < length(); i++) { - if (!invert_find) { - if (case_sense && matches_any(get(i), to_find)) return i; - else if (imatches_any(get(i), to_find)) return i; - } else { - // case-sensitivity is not used for inverted finds. -//printf("fwd posn=%d char=%c", i, get(i)); - if (matches_none(get(i), to_find)) return i; - } - } - } - return common::NOT_FOUND; -} - -int astring::find(const astring &to_find, int posn, bool reverse) const -{ return str_find(to_find, posn, reverse, true); } - -int astring::ifind(const astring &to_find, int posn, bool reverse) const -{ return str_find(to_find, posn, reverse, false); } - -int astring::str_find(const astring &to_find, int posn, bool reverse, - bool case_sense) const -{ - bounds_return(posn, 0, end(), common::OUT_OF_RANGE); - if (!to_find.length()) return common::BAD_INPUT; - - // skip some steps by finding the first place that the first character of - // the string resides in our string. - if (case_sense) - posn = find(to_find[0], posn, reverse); - else posn = ifind(to_find[0], posn, reverse); - if (posn < 0) return common::NOT_FOUND; - -//hmmm: there is a better way to do this loop in terms of the number of -// comparisons performed. knuth morris pratt algorithm? - if (case_sense) { -//hmmm: this could use strncmp too? - if (reverse) { - if (posn > length() - to_find.length()) - posn = length() - to_find.length(); - for (int i = posn; i >= 0; i--) - if (!memcmp((void *)&observe()[i], (void *)to_find.observe(), - to_find.length())) - return i; - } else { - const int find_len = to_find.length(); - const int str_len = length(); - const char first_char = to_find[0]; - bounds_return(posn, 0, str_len - find_len, common::OUT_OF_RANGE); - for (int i = posn - 1; - ( ( (i = find(first_char, i + 1)) >= 0) - && (str_len - i >= find_len) ); no_increment) { - if (!memcmp((void *)&observe()[i], (void *)to_find.observe(), - to_find.length())) - return i; - } - } - } else { - // not case-sensitive. - if (reverse) { - if (posn > length() - to_find.length()) - posn = length() - to_find.length(); - for (int i = posn; i >= 0; i--) - if (!strncasecmp(&observe()[i], to_find.observe(), to_find.length())) - return i; - } else { - bounds_return(posn, 0, length() - to_find.length(), common::OUT_OF_RANGE); - for (int i = posn; i < length() - to_find.length() + 1; i++) - if (!strncasecmp(&observe()[i], to_find.observe(), to_find.length())) - return i; - } - } - return common::NOT_FOUND; -} - -astring astring::operator + (const astring &s1) const -{ - astring to_return(*this); - to_return += s1; - return to_return; -} - -char &astring::operator [] (int position) -{ - if (position < 0) position = 0; - if (position > end()) position = 0; - abyte &found = c_character_manager.use(position); - char &to_return = *((char *)(&found)); - return to_return; -} - -const char &astring::operator [] (int position) const -{ - if (position < 0) position = 0; - if (position > end()) position = 0; - const abyte &found = c_character_manager.get(position); - const char &to_return = *((const char *)(&found)); - return to_return; -} - -int astring::convert(int default_value) const -{ - if (!length()) return default_value; - int to_return; - int fields = sscanf(observe(), "%d", &to_return); - if (fields < 1) return default_value; - return to_return; -} - -long astring::convert(long default_value) const -{ - if (!length()) return default_value; - long to_return; - int fields = sscanf(observe(), "%ld", &to_return); - if (fields < 1) return default_value; - return to_return; -} - -float astring::convert(float default_value) const -{ - if (!length()) return default_value; - float to_return; - int fields = sscanf(observe(), "%f", &to_return); - if (fields < 1) return default_value; - return to_return; -} - -double astring::convert(double default_value) const -{ - if (!length()) return default_value; - double to_return; - int fields = sscanf(observe(), "%lf", &to_return); - if (fields < 1) return default_value; - return to_return; -} - -astring &astring::operator += (const char *s1) -{ - if (!s1) return *this; - int len = length(); - c_character_manager.insert(len, int(strlen(s1))); - memmove((char *)&c_character_manager[len], s1, int(strlen(s1))); - return *this; -} - -astring &astring::operator += (char s1) -{ - int len = length(); - c_character_manager.insert(len, 1); - c_character_manager.put(len, s1); - return *this; -} - -bool astring::compare(const astring &to_compare, int start_first, - int start_second, int count, bool case_sensitive) const -{ - bounds_return(start_first, 0, end(), false); - bounds_return(start_second, 0, to_compare.end(), false); - bounds_return(start_first + count, start_first, length(), false); - bounds_return(start_second + count, start_second, to_compare.length(), false); - - if (!case_sensitive) { - return !strncasecmp(&observe()[start_first], - &to_compare.observe()[start_second], count); - } else { - return !memcmp((void *)&observe()[start_first], - (void *)&to_compare.observe()[start_second], count); - } -} - -/* -int astring::icompare(const char *to_compare, int length_in) const -{ - if (!length_in) return 0; // nothing is equal to nothing. - int real_len = length_in; - // if they're passing a negative length, we use the full length. - if (negative(length_in)) - real_len = length(); - // if we have no length, make the obvious returns now. - int to_compare_len = int(strlen(to_compare)); - if (!real_len) return to_compare_len? -1 : 0; - // if the second string is empty, it's always less than the non-empty. - if (!to_compare_len) return 1; - int to_return = strncasecmp(observe(), to_compare, real_len); - if (negative(length_in) && !to_return && (to_compare_len > length()) ) { - // catch special case for default length when the two are equal except - // that the second string is longer--this means the first is less than - // second, not equal. - return -1; - } else - return to_return; -} -*/ - -/* -bool astring::oy_icompare(const astring &to_compare, int start_first, - int start_second, int count) const -{ - bounds_return(start_first, 0, end(), false); - bounds_return(start_second, 0, to_compare.end(), false); - bounds_return(start_first + count, start_first, length(), false); - bounds_return(start_second + count, start_second, to_compare.length(), false); - const char *actual_first = this->observe() + start_first; - const char *actual_second = to_compare.observe() + start_second; - return !strncasecmp(actual_first, actual_second, count); -} -*/ - -bool astring::substring(astring &target, int start, int bender) const -{ - target.reset(); - if (bender < start) return false; - const int last = end(); // final position that's valid in the string. - bounds_return(start, 0, last, false); - bounds_return(bender, 0, last, false); - target.reset(UNTERMINATED, observe() + start, bender - start + 1); - return true; -} - -astring astring::substring(int start, int end) const -{ - astring to_return; - substring(to_return, start, end); - return to_return; -} - -astring astring::middle(int start, int count) -{ return substring(start, start + count - 1); } - -astring astring::left(int count) -{ return substring(0, count - 1); } - -astring astring::right(int count) -{ return substring(end() - count + 1, end()); } - -void astring::insert(int position, const astring &to_insert) -{ - bounds_return(position, 0, length(), ); - if (this == &to_insert) { - astring copy_of_me(to_insert); - insert(position, copy_of_me); // not recursive because no longer == me. - } else { - c_character_manager.insert(position, to_insert.length()); - c_character_manager.overwrite(position, to_insert.c_character_manager, - to_insert.length()); - } -} - -bool astring::replace(const astring &tag, const astring &replacement) -{ - int where = find(tag); - if (negative(where)) return false; - zap(where, where + tag.end()); - insert(where, replacement); - return true; -} - -bool astring::replace_all(const astring &to_replace, const astring &new_string) -{ - bool did_any = false; - for (int i = 0; i < length(); i++) { - int indy = find(to_replace, i); - if (negative(indy)) break; // get out since there are no more matches. - i = indy; // update our position to where we found the string. - zap(i, i + to_replace.length() - 1); // remove old string. - insert(i, new_string); // plug the new string into the old position. - i += new_string.length() - 1; // jump past what we replaced. - did_any = true; - } - return did_any; -} - -bool astring::replace_all(char to_replace, char new_char) -{ - bool did_any = false; - for (int i = 0; i < length(); i++) { - if (get(i) == to_replace) { - put(i, new_char); - did_any = true; - } - } - return did_any; -} - -bool astring::matches(const astring &match_list, char to_match) -{ - for (int i = 0; i < match_list.length(); i++) - if (to_match == match_list.get(i)) return true; - return false; -} - -void astring::strip(const astring &strip_list, how_to_strip way) -{ - if (way & FROM_FRONT) - while (length() && matches(strip_list, get(0))) - zap(0, 0); - - if (way & FROM_END) - while (length() && matches(strip_list, get(end()))) - zap(end(), end()); -} - -int astring::packed_size() const { return length() + 1; } - -void astring::pack(byte_array &target) const -{ attach(target, (char *)c_character_manager.observe()); } - -bool astring::unpack(byte_array &source) -{ return detach(source, *this); } - -///int astring::icompare(const astring &to_compare, int length_in) const -///{ return icompare(to_compare.observe(), length_in); } - -/* -int astring::slow_strncasecmp(const char *first, const char *second, int length) -{ - int len1 = int(strlen(first)); - int len2 = int(strlen(second)); - if (!length) return 0; // no characters are equal to none. - if (!len1 && !len2) return 0; // equal as empty. - if (!len1 && len2) return -1; // first < second. - if (len1 && !len2) return 1; // first > second. - if (positive(length)) { - len1 = minimum(length, len1); - len2 = minimum(length, len2); - } - for (int i = 0; i < len1; i++) { - if (i > len2 - 1) return 1; // first > second, had more length. - if (simple_lower(first[i]) < simple_lower(second[i])) - return -1; // first < second. - if (simple_lower(first[i]) > simple_lower(second[i])) - return 1; // first > second. - } - // at this point we know second is equal to first up to the length of - // first. - if (len2 > len1) return -1; // second was longer and therefore greater. - return 0; // equal. -} -*/ - -////////////// - -a_sprintf::a_sprintf() : astring() {} - -a_sprintf::a_sprintf(const astring &s) : astring(s) {} - -a_sprintf::a_sprintf(const char *initial, ...) -: astring() -{ - if (!initial) return; - va_list args; - va_start(args, initial); - base_sprintf(initial, args); - va_end(args); -} - -////////////// - -void attach(byte_array &packed_form, const char *to_attach) -{ - const int len = int(strlen(to_attach)); - const int old_pos = packed_form.last(); - packed_form.insert(old_pos + 1, len + 1); - memmove((char *)packed_form.observe() + old_pos + 1, to_attach, len + 1); -} - -bool detach(byte_array &packed_form, astring &to_detach) -{ - if (!packed_form.length()) return false; - // locate the zero termination if possible. - const void *zero_posn = memchr(packed_form.observe(), '\0', - packed_form.length()); - // make sure we could find the zero termination. - if (!zero_posn) { - // nope, never saw a zero. good thing we checked. - to_detach.reset(); - return false; - } - // set the string up using a standard constructor since we found the zero - // position; we know the string constructor will be happy. - to_detach = (char *)packed_form.observe(); - // compute the length of the string we found based on the position of the - // zero character. - int find_len = int((abyte *)zero_posn - packed_form.observe()); - // whack the portion of the array that we consumed. - packed_form.zap(0, find_len); - return true; -} - -////////////// - -// contract fulfillment area. - -base_string &astring::concatenate_string(const base_string &s) -{ - const astring *cast = dynamic_cast(&s); - if (cast) *this += *cast; - else *this += astring(s.observe()); - return *this; -} - -base_string &astring::concatenate_char(char c) -{ - *this += c; - return *this; -} - -base_string &astring::assign(const base_string &s) -{ - const astring *cast = dynamic_cast(&s); - if (cast) *this = *cast; - else *this = astring(s.observe()); - return *this; -} - -base_string &astring::upgrade(const char *s) -{ - *this = s; - return *this; -} - -bool astring::sub_string(base_string &target, int start, int end) const -{ - astring *cast = dynamic_cast(&target); - if (!cast) throw "error: astring::sub_string: unknown type"; - return substring(*cast, start, end); -} - -bool astring::sub_compare(const base_string &to_compare, int start_first, - int start_second, int count, bool case_sensitive) const -{ - const astring *cast = dynamic_cast(&to_compare); - if (cast) return compare(*cast, start_first, start_second, count, case_sensitive); - else return compare(astring(to_compare.observe()), start_first, start_second, - count, case_sensitive); -} - -void astring::insert(int position, const base_string &to_insert) -{ - const astring *cast = dynamic_cast(&to_insert); - if (cast) this->insert(position, *cast); - else this->insert(position, astring(to_insert.observe())); -} - -} //namespace. - diff --git a/core/library/basis/astring.h b/core/library/basis/astring.h deleted file mode 100644 index 810e3dd5..00000000 --- a/core/library/basis/astring.h +++ /dev/null @@ -1,468 +0,0 @@ -#ifndef ASTRING_CLASS -#define ASTRING_CLASS - -/*****************************************************************************\ -* * -* Name : astring * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "base_string.h" -#include "byte_array.h" -#include "contracts.h" - -#include - -namespace basis { - -//! Provides a dynamically resizable ASCII character string. -/*! - It mimics the standard (char *) type, but provides a slew of helpful - methods as well as enforcing bounds checking on the underlying array. -*/ - -class astring -: public virtual base_string, - public virtual hoople_standard -{ -public: - astring(); - //!< constructs an empty string. - - astring(const char *initial); - //!< constructs a copy of the string passed in "initial". - - astring(char c, int repeat); - //!< constructs a string with "repeat" characters of "c" in it. - /*!< if "c" is the null character (i.e., equal to zero), then the resulting - string will have "repeat" space characters in it. */ - - astring(const astring &s); - //!< Constructs a copy of the string "s". - - astring(const base_string &initial); - //!< constructs a string from the base class. - - enum special_flag { UNTERMINATED = 62, SPRINTF = 84 }; - astring(special_flag way, const char *s, ...); - //!< constructor that sports a few variable parameter constructions. - /*!< - For a flag of "UNTERMINATED", the constructor expects the third - parameter to be an integer, and then it copies that number of - characters from the C-string "s" without assuming that "s" is zero - terminated. - - For a flag of "SPRINTF", a string is constructed using the format specifier - in "s" in a manner similar to the standard library "sprintf" function - (see the standard library for ). If there are no "%" codes in - "s", then the constructor just copies "s" without modification. If "%" - codes are in the character array, then any additional arguments (...) are - interpreted as they would be by sprintf. The length of the - constructed string is tailored to fit the actual contents. If "s" is - null, then the resulting string will be empty. Currently, the "*" - specifier for variable length fields is not supported. */ - - virtual ~astring(); - //!< destroys any storage for the string. - - DEFINE_CLASS_NAME("astring"); - - virtual int comparator(const astring &s2) const; - //!< helps to fulfill orderable contract. - - int length() const; - //!< Returns the current length of the string. - /*!< The length returned does not include the terminating null character - at the end of the string. */ - - int end() const { return length() - 1; } - //!< returns the index of the last (non-null) character in the string. - /*!< If there is no content in the string, then a negative value is - returned. */ - - bool empty() const { return !length(); } - //!< empty() reports if the string is empty, that is, of zero length(). - bool non_empty() const { return !empty(); } - //!< non_empty() reports if the string has some contents. - bool operator ! () const { return empty(); } - //!< the negation operator returns true if the string is empty. - /*!< it can be used in expressions in a readable way, for example: - if (!my_string) { it_is_empty; } */ - bool t() const { return !empty(); } - //!< t() is a shortcut for the string being "true", as in non-empty. - /*!< the logic here is that the string is not false because it's not - empty. for example: if (my_string.t()) { it_is_not_empty; } */ - - static const astring &empty_string(); - //!< useful wherever empty strings are needed, e.g., function defaults. - /*!< note that this is implemented in the opsystem library to avoid bad - issues with static objects mixed into multiple dlls from a static - library. */ - - virtual const char *observe() const; - //!< observes the underlying pointer to the zero-terminated string. - /*!< this does not allow the contents to be modified. this method should - never return NIL. */ - const char *c_str() const { return observe(); } - //!< synonym for observe. mimics the STL method name. - const char *s() const { return observe(); } - //!< synonym for observe. the 's' stands for "string", if that helps. - - virtual char get(int index) const; - //!< a constant peek at the string's internals at the specified index. - - virtual char *access(); - //!< provides access to the actual string held. - /*!< this should never return NIL. be very careful with the returned - pointer: don't destroy or corrupt its contents (e.g., do not mess with - its zero termination). */ - char *c_str() { return access(); } - //!< synonym for access. mimics the STL method. - char *s() { return access(); } - //!< synonym for access. - - char &operator [] (int position); - //!< accesses individual characters in "this" string. - /*!< if the "position" is out of range, the return value is - not meaningful. */ - const char &operator [] (int position) const; - //!< observes individual characters in "this" string. - /*!< if the "position" is out of range, the return value is - not meaningful. */ - - virtual void put(int position, char to_put) { (*this)[position] = to_put; } - //!< stores the character "to_put" at index "position" in the string. - - astring &sprintf(const char *s, ...); - //!< similar to the SPRINTF constructor, but works on an existing string. - /*!< any existing contents in the string are wiped out. */ - - int convert(int default_value) const; - //!< Converts the string into a corresponding integer. - /*!< The conversion starts at index 0 in "this" string, and stores it in - "value". If a valid integer was found, it is returned. otherwise, the - "default_value" is returned. NOTE: be careful of implicit conversions - here; the "default_value" for any of these functions must either be an - object of the exact type needed or must be cast to that type. */ - long convert(long default_value) const; - //!< converts the string to a long integer. - float convert(float default_value) const; - //!< converts the string to a floating point number. - double convert(double default_value) const; - //!< converts the string to a double precision floating point number. - - bool equal_to(const char *that) const; - //!< returns true if "that" is equal to this. - - bool iequals(const astring &that) const; - //!< returns true if this is case-insensitively equal to "that". - bool iequals(const char *that) const; - //!< returns true if this is case-insensitively equal to "that". - - bool compare(const astring &to_compare, int start_first, - int start_second, int count, bool case_sensitive) const; - //!< Compares "this" string with "to_compare". - /*!< The "start_first" is where the comparison begins in "this" string, - and "start_second" where it begins in the "to_compare". The "count" is - the number of characters to compare between the two strings. If either - index is out of range, or "count"-1 + either index is out of range, then - compare returns false. If the strings differ in that range, false is - returned. Only if the strings have identical contents in the range is - true returned. */ - - bool begins(const astring &maybe_prefix) const - { return compare(maybe_prefix, 0, 0, maybe_prefix.length(), true); } - //!< Returns true if "this" string begins with "maybe_prefix". - - bool ibegins(const astring &maybe_prefix) const - { return compare(maybe_prefix, 0, 0, maybe_prefix.length(), false); } - //!< a case-insensitive method similar to begins(). - - //! returns true if this string ends with "maybe_suffix". - bool ends(const astring &maybe_suffix) const { - const int diff = length() - maybe_suffix.length(); - return (diff >= 0) && compare(maybe_suffix, diff, 0, maybe_suffix.length(), true); - } - //!< a case-insensitive method similar to ends(). - bool iends(const astring &maybe_suffix) const { - const int diff = length() - maybe_suffix.length(); - return (diff >= 0) && compare(maybe_suffix, diff, 0, maybe_suffix.length(), false); - } - - astring &operator = (const astring &s); - //!< Sets the contents of this string to "s". - astring &operator = (const char *s); - //!< Sets the contents of this string to "s". - - void reset() { zap(0, end()); } - //!< clears out the contents string. - - void reset(special_flag way, const char *s, ...); - //!< operates like the constructor that takes a 'special_flag'. - - void copy(char *to_stuff, int count) const; - //!< Copies a maximum of "count" characters from this into "to_stuff". - /*!< The target "to_stuff" is a standard C-string. The terminating zero - from this string is also copied. BE CAREFUL: if "count"+1 is greater than - the allocated length of the C-string "to_stuff", then an invalid memory - write will occur. keep in mind that the terminating zero will be put at - position "count" in the C-string if the full "count" of characters are - copied. */ - void stuff(char *to_stuff, int count) const { copy(to_stuff, count); } - //!< a synonym for copy(). - - astring operator + (const astring &s) const; - //!< Returns the concatenation of "this" and "s". - - astring &operator += (const astring &s); - //!< Modifies "this" by concatenating "s" onto it. - - astring &operator += (const char *s); // this is efficient. - //!< synonym for the concatenation operator but uses a char pointer instead. - astring operator + (const char *s) const { return *this + astring(s); } - //!< synonym for the concatenation operator but uses a char pointer instead. - // this method is not efficient. - - astring &operator += (char c); //!< concatenater for single characters. - - int find(char to_find, int position = 0, bool reverse = false) const; - //!< Locates "to_find" in "this". - /*!< find returns the index of "to_find" or "NOT_FOUND". find starts - looking at "position". find returns "OUT_OF_RANGE" if the position is - beyond the bounds of "this". */ - int find(const astring &to_find, int posn = 0, bool reverse = false) const; - //!< finds "to_find" in this string. - - int ifind(char to_find, int position = 0, bool reverse = false) const; - //!< like the find() methods above, but case-insensitive. - int ifind(const astring &to_find, int posn = 0, bool reverse = false) const; - //!< like the find() methods above, but case-insensitive. - - int find_any(const char *to_find, int position = 0, - bool reverse = false) const; - //!< searches for any of the characters in "to_find". - /*!< the first occurrence of any of those is returned, or a negative - number is returned if no matches are found. */ - int ifind_any(const char *to_find, int position = 0, - bool reverse = false) const; - //!< searches case-insensitively for any of the characters in "to_find". - /*!< the first occurrence of any of those is returned, or a negative number - is returned if none are found. */ - int find_non_match(const char *to_find, int position = 0, - bool reverse = false) const; - //!< searches for any character that is not in "to_find" and returns index. - - bool contains(const astring &to_find) const; - //!< Returns true if "to_find" is contained in this string or false if not. - - bool substring(astring &target, int start, int end) const; - //!< a version that stores the substring in an existing "target" string. - - astring substring(int start, int end) const; - //!< Returns the segment of "this" between the indices "start" and "end". - /*!< An empty string is returned if the indices are out of range. */ - - // helper methods similar to other string's choppers. - astring middle(int start, int count); - //!< returns the middle of the string from "start" with "count" characters. - astring left(int count); - //!< returns the left "count" characters from the string. - astring right(int count); - //!< returns the right "count" characters from the string. - - void pad(int length, char padding = ' '); - //!< makes the string "length" characters long. - /*!< this string is padded with the "padding" character if the string is - less than that length initially. */ - void trim(int length); - //!< shortens the string to "length" if it's longer than that. - - void insert(int position, const astring &to_insert); - //!< Copies "to_insert" into "this" at the "position". - /*!< Characters at the index "position" and greater are moved over. */ - virtual void zap(int start, int end); - //!< Deletes the characters between "start" and "end" inclusively. - /*!< C++ array conventions are used (0 through length()-1 are valid). If - either index is out of bounds, then the string is not modified. */ - - void to_lower(); - //!< to_lower modifies "this" by replacing capitals with lower-case. - /*!< every capital letter is replaced with the corresponding lower case - letter (i.e., A becomes a). */ - void to_upper(); - //!< to_upper does the opposite of to_lower (that is, q becomes Q). - astring lower() const; - //!< like to_lower(), but returns a new string rather than modifying this. - astring upper() const; - //!< like to_upper(), but returns a new string rather than modifying this. - - bool replace(const astring &tag, const astring &replacement); - //!< replaces the first occurrence of "tag" text with the "replacement". - /*!< true is returned if the "tag" was actually found and replaced. */ - bool replace_all(char to_replace, char new_char); - //!< changes all occurrences of "to_replace" with "new_char". - bool replace_all(const astring &to_replace, const astring &new_string); - //! changes all occurrences of "to_replace" into "new_string". - - void shrink(); - //!< resizes the string to its minimum possible length. - /*!< this fixes any situations where a null character has been inserted - into the middle of the string. the string is truncated after the first - null charater encountered and its size is corrected. this also repairs - any case where the string was originally longer than it is now. */ - - enum how_to_strip { FROM_FRONT = 1, FROM_END = 2, FROM_BOTH_SIDES = 3 }; - //!< an enumeration describing the strip operations. - - void strip(const astring &strip_list, how_to_strip way = FROM_BOTH_SIDES); - //!< strips all chars from "strip_list" out of "this" given the "way". - - void strip_spaces(how_to_strip way = FROM_BOTH_SIDES) - { strip(" ", way); } - //!< removes excess space characters from string's beginning, end or both. - - void strip_white_spaces(how_to_strip way = FROM_BOTH_SIDES) - { strip(" \t", way); } - //!< like strip_spaces, but includes tabs in the list to strip. - - static bool matches(const astring &match_list, char to_match); - //!< returns true if "to_match" is found in the "match_list" string. - - int packed_size() const; - //!< Reports the size required to pack this string into a byte array. - - void pack(byte_array &target) const; - //!< stores this string in the "target". it can later be unpacked again. - bool unpack(byte_array &source); - //!< retrieves a string (packed with pack()) from "source" into this string. - /*!< note that the string is grabbed from the array destructively; whatever - portion of the byte array was used to store the string will be removed from - the head of the array. */ - -//hmmm: rename this--it is not a simple icompare, but a strncasecmp analogue. -// int icompare(const astring &to_compare, int length = -1) const; - //!< provides a case insensitive comparison routine. - /*!< this uses the best methods available (that is, it uses a system - function if one exists). the string "to_compare" is compared with this - string. if the "length" is negative, then this entire string is compared - with the entire string "to_compare". otherwise, only "length" characters - from this string are compared. if this string is before "to_compare" in - a lexicographic ordering (basically alphabetical), then a negative number - is returned. if this string is after "to_compare", then a positive number - is returned. zero is returned if the two strings are equal for the extent - of interest. */ - -/// int icompare(const char *to_compare, int length = -1) const; - //!< a version of the above for raw character pointers. - -/// static int slow_strncasecmp(const char *first, const char *second, -/// int length = -1); - //!< a replacement for strncasecmp on platforms without them. - /*!< this is slow because it cannot rely on OS methods to perform the - comparison. if the "length" is negative, then the entire string "first" - is compared to "second". otherwise just "length" characters are compared. - this follows the standard library strncasecmp method: the return value can - be in three states: negative, zero and positive. zero means the strings - are identical lexicographically , whereas less than zero means - "this_string" is less than "to_compare" and greater than zero means - "this_string" is greater than "to_compare". */ - - // play-yard for implementing base class requirements. - - // these implement the orderable and equalizable interfaces. - virtual bool equal_to(const equalizable &s2) const; - virtual bool less_than(const orderable &s2) const; - - virtual base_string &concatenate_string(const base_string &s); - virtual base_string &concatenate_char(char c); - virtual base_string &assign(const base_string &s); - virtual base_string &upgrade(const char *s); - virtual bool sub_string(base_string &target, int start, int end) const; - virtual bool sub_compare(const base_string &to_compare, int start_first, - int start_second, int count, bool case_sensitive) const; - virtual void insert(int position, const base_string &to_insert); - virtual void text_form(base_string &state_fill) const; - -private: - byte_array c_character_manager; - //!< hides the real object responsible for implementing much of the class. - - // the real find methods. - int char_find(char to_find, int position, bool reverse, - bool case_sense) const; - // if "invert_find" is true, then non-matches are reported instead of matches. - int char_find_any(const astring &to_find, int position, bool reverse, - bool case_sense, bool invert_find = false) const; - int str_find(const astring &to_find, int posn, bool reverse, - bool case_s) const; - - // the functions below are used in the formatting string constructor. -public: // only for base_sprintf. - astring &base_sprintf(const char *s, va_list &args); -private: - char *const *c_held_string; //!< peeks into the actual pointer for debugging. - - void seek_flag(const char *&traverser, char *flag_chars, bool &failure); - //!< looks for optional flag characters. - void seek_width(const char *&traverser, char *width_chars); - //!< looks for optional width characters. - void seek_precision(const char *&traverser, char *precision_chars); - //!< looks for optional precision characters. - void seek_modifier(const char *&traverser, char *modifier_char); - //!< looks for optional modifier characters. - void get_type_character(const char *&traverser, va_list &args, - astring &output_string, const char *flag_chars, - const char *width_chars, const char *precision_chars, - const char *modifier_chars); - /*!< the required character in a format specifier is either grabbed here or - the other characters are put into the ouput string without formatting. - the "X"_char variables should have been previously gathered by the - seek_"X" functions. */ - - public: byte_array &get_implementation(); private: // for test programs only.... -}; - -////////////// - -//! a_sprintf is a specialization of astring that provides printf style support. -/*! it makes it much easier to call the SPRINTF style constructor but is -otherwise identical to an astring. */ - -class a_sprintf : public astring -{ -public: - a_sprintf(); - a_sprintf(const char *initial, ...); - a_sprintf(const astring &s); -}; - -////////////// - -typedef bool string_comparator_function(const astring &a, const astring &b); - //!< returns true if the strings "a" and "b" are considered equal. - /*!< this provides a prototype for the equality operation, which allows the - notion of equality to be redefined according to a particular function's - implementation. */ - -bool astring_comparator(const astring &a, const astring &b); - //!< implements a string comparator that just does simple astring ==. - -////////////// - -void attach(byte_array &packed_form, const char *to_attach); - //!< Packs a character string "to_attach" into "packed_form". -bool detach(byte_array &packed_form, astring &to_detach); - //!< Unpacks a character string "to_attach" from "packed_form". - -} //namespace. - -#endif - diff --git a/core/library/basis/base_string.h b/core/library/basis/base_string.h deleted file mode 100644 index c2c2cf45..00000000 --- a/core/library/basis/base_string.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef BASE_STRING_CLASS -#define BASE_STRING_CLASS - -/*****************************************************************************\ -* * -* Name : base_string * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//hmmm: some of these methods could be pulled out to a base_array class. -// that would be a nice further abstraction. - -#include "contracts.h" - -namespace basis { - -//! Defines the base class for all string processing objects in hoople. - -class base_string : public virtual orderable -{ -public: - virtual int length() const = 0; - //!< Returns the current length of the string. - /*!< The length returned does not include the terminating null character - at the end of the string. */ - - virtual const char *observe() const = 0; - //!< observes the underlying pointer to the zero-terminated string. - /*!< this does not allow the contents to be modified. this method should - never return NIL. */ - - virtual char *access() = 0; - //!< provides access to the actual string held. - /*!< this should never return NIL. be very careful with the returned - pointer: don't destroy or corrupt its contents (e.g., do not mess with - its zero termination). */ - - virtual char get(int index) const = 0; - //!< a constant peek at the string's internals at the specified index. - - virtual void put(int position, char to_put) = 0; - //!< stores the character "to_put" at index "position" in the string. - - virtual bool sub_compare(const base_string &to_compare, int start_first, - int start_second, int count, bool case_sensitive) const = 0; - //!< Compares "this" string with "to_compare". - /*!< The "start_first" is where the comparison begins in "this" string, - and "start_second" where it begins in the "to_compare". The "count" is - the number of characters to compare between the two strings. If either - index is out of range, or "count"-1 + either index is out of range, then - compare returns false. If the strings differ in that range, false is - returned. Only if the strings have identical contents in the range is - true returned. If case-sensitive is false, then matches will not require - the caps and lower-case match. */ - - virtual base_string &assign(const base_string &s) = 0; - //!< Sets the contents of this string to "s". - - virtual base_string &upgrade(const char *s) = 0; - //!< Sets the contents of this string to "s". - - virtual void insert(int position, const base_string &to_insert) = 0; - //!< Copies "to_insert" into "this" at the "position". - /*!< Characters at the index "position" and greater are moved over. */ - - virtual void zap(int start, int end) = 0; - //!< Deletes the characters between "start" and "end" inclusively. - /*!< C++ array conventions are used (0 through length()-1 are valid). If - either index is out of bounds, then the string is not modified. */ - - virtual base_string &concatenate_string(const base_string &s) = 0; - //!< Modifies "this" by concatenating "s" onto it. - - virtual base_string &concatenate_char(char c) = 0; - //!< concatenater for single characters. - - virtual bool sub_string(base_string &target, int start, int end) const = 0; - //!< Gets the segment of "this" between the indices "start" and "end". - /*!< false is returned if the range is invalid. */ - - //! sets this string's contents equal to the contents of "to_copy". - /*! this assignment ensures that setting the base class to derived versions will - succeed; otherwise, a base class copy does very little. */ - virtual base_string &operator =(const base_string &to_copy) { return assign(to_copy); } -}; - -} //namespace. - -#endif - diff --git a/core/library/basis/byte_array.h b/core/library/basis/byte_array.h deleted file mode 100644 index cd5239fa..00000000 --- a/core/library/basis/byte_array.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef BYTE_ARRAY_CLASS -#define BYTE_ARRAY_CLASS - -/*****************************************************************************\ -* * -* Name : byte_array * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "array.h" -#include "base_string.h" -#include "contracts.h" -#include "definitions.h" - -#include // for memcmp. - -namespace basis { - -//! A very common template for a dynamic array of bytes. -/*! - byte_array provides a simple wrapper around array, but with the - exponential growth and simple copy modes automatically enabled. - Note that it is almost always best to forward declare byte_arrays in ones - own headers rather than including this header. -*/ - -class byte_array : public array, public virtual orderable -{ -public: - byte_array(int number = 0, const abyte *initial_contents = NIL) - : array(number, initial_contents, SIMPLE_COPY | EXPONE) {} - //!< constructs an array of "number" bytes from "initial_contents". - - byte_array(const byte_array &to_copy) - : root_object(), array(to_copy) {} - //!< constructs an array bytes by copying the "to_copy" array. - - byte_array(const array &to_copy) : array(to_copy) {} - //!< constructs an array bytes by copying the "to_copy" array. - - virtual ~byte_array() {} - - DEFINE_CLASS_NAME("byte_array"); - - //!< returns an array of zero bytes. - /*!< note that this is implemented in the opsystem library to avoid bad - issues with static objects mixed into multiple dlls from a static - library. */ - static const byte_array &empty_array() - { static byte_array g_empty; return g_empty; } - - // these implement the orderable and equalizable interfaces. - virtual bool equal_to(const equalizable &s2) const { - const byte_array *s2_cast = dynamic_cast(&s2); - if (!s2_cast) throw "error: byte_array::==: unknown type"; - return comparator(*s2_cast) == 0; - } - virtual bool less_than(const orderable &s2) const { - const byte_array *s2_cast = dynamic_cast(&s2); - if (!s2_cast) throw "error: byte_array::<: unknown type"; - return comparator(*s2_cast) < 0; - } - - int comparator(const byte_array &s2) const { - return memcmp(observe(), s2.observe(), length()); - } -}; - -////////////// - -//! A base class for objects that can pack into an array of bytes. -/*! - A packable is an abstract object that represents any object that can - be transformed from a potentially deep form into an equivalent flat - form. The flat form is a simple extent of memory stored as bytes. -*/ - -class packable : public virtual root_object -{ -public: - virtual void pack(byte_array &packed_form) const = 0; - //!< Creates a packed form of the packable object in "packed_form". - /*!< This must append to the data in "packed_form" rather than clearing - prior contents. */ - - virtual bool unpack(byte_array &packed_form) = 0; - //!< Restores the packable from the "packed_form". - /*!< This object becomes the unpacked form, and therefore must lose any of - its prior contents that depend on the data in "packed_form". This is up to - the derived unpack function to figure out. The "packed_form" is modified - by extracting all of the pieces that are used for this object; the - remainder stays in "packed_form". true is returned if the unpacking was - successful. */ - - virtual int packed_size() const = 0; - //!< Estimates the space needed for the packed structure. -}; - -////////////// - -// the two templates below can be used to add or remove objects from an array -// of bytes. NOTE: the functions below will only work with objects that are -// already platform-independent. it's better to make structures packable by -// using the attach and detach functions in the "packable" library. - -//! attach_flat() places a copy of "attachment" onto the array of bytes. -template -void attach_flat(byte_array &target, const contents &attachment) -{ target.concatenate(byte_array(sizeof(attachment), (abyte *)&attachment)); } - -//! detach_flat() pulls the "detached" object out of the array of bytes. -template -bool detach_flat(byte_array &source, contents &detached) -{ - if (sizeof(detached) > source.length()) return false; - detached = *(contents *)source.observe(); - source.zap(0, sizeof(detached) - 1); - return true; -} - -} // namespace. - -#endif - diff --git a/core/library/basis/common_outcomes.cpp b/core/library/basis/common_outcomes.cpp deleted file mode 100644 index a30ca268..00000000 --- a/core/library/basis/common_outcomes.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/*****************************************************************************\ -* * -* Name : common_outcomes * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "common_outcomes.h" - -namespace basis { - -const char *common::outcome_name(const outcome &to_name) -{ - switch (to_name.value()) { - case OKAY: return "OKAY"; - case NOT_IMPLEMENTED: return "NOT_IMPLEMENTED"; - case OUT_OF_RANGE: return "OUT_OF_RANGE"; - case NOT_FOUND: return "NOT_FOUND"; - case BAD_INPUT: return "BAD_INPUT"; - case BAD_TYPE: return "BAD_TYPE"; - case IS_FULL: return "IS_FULL"; - case IS_EMPTY: return "IS_EMPTY"; - case IS_NEW: return "IS_NEW"; - case EXISTING: return "EXISTING"; - case FAILURE: return "FAILURE"; - case OUT_OF_MEMORY: return "OUT_OF_MEMORY"; - case ACCESS_DENIED: return "ACCESS_DENIED"; - case IN_USE: return "IN_USE"; - case UNINITIALIZED: return "UNINITIALIZED"; - case TIMED_OUT: return "TIMED_OUT"; - case GARBAGE: return "GARBAGE"; - case NO_SPACE: return "NO_SPACE"; - case DISALLOWED: return "DISALLOWED"; - case INCOMPLETE: return "INCOMPLETE"; - case NO_HANDLER: return "NO_HANDLER"; - case NONE_READY: return "NONE_READY"; - case INVALID: return "INVALID"; - case PARTIAL: return "PARTIAL"; - case NO_LICENSE: return "NO_LICENSE"; - case UNEXPECTED: return "UNEXPECTED"; - case ENCRYPTION_MISMATCH: return "ENCRYPTION_MISMATCH"; - default: return "UNKNOWN_OUTCOME"; - } -} - -} // namespace. - diff --git a/core/library/basis/common_outcomes.h b/core/library/basis/common_outcomes.h deleted file mode 100644 index 893a5cb2..00000000 --- a/core/library/basis/common_outcomes.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef COMMON_OUTCOMES_CLASS -#define COMMON_OUTCOMES_CLASS - -/*****************************************************************************\ -* * -* Name : common_outcomes * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "outcome.h" - -namespace basis { - -//! the "common" class defines our common_outcomes. -class common -{ -public: - //! these outcomes are returned by classes that use the basic HOOPLE support. - /*! More complicated classes will need to define their own outcome values to - describe what occurred during the processing of requests. */ - enum outcomes { - DEFINE_API_OUTCOME(OKAY, 0, "Everything is just fine"), - DEFINE_API_OUTCOME(NOT_IMPLEMENTED, -1, - "The invoked method is unimplemented"), - DEFINE_API_OUTCOME(OUT_OF_RANGE, -2, "The value specified was out " - "of bounds"), - DEFINE_API_OUTCOME(NOT_FOUND, -3, "The item sought is not present"), - DEFINE_API_OUTCOME(BAD_INPUT, -4, "Precondition failure--the parameters " - "were inappropriate"), - DEFINE_API_OUTCOME(BAD_TYPE, -5, "The objects are of incompatible types"), - DEFINE_API_OUTCOME(IS_FULL, -6, "There is no room in the storage facility"), - DEFINE_API_OUTCOME(IS_EMPTY, -7, "The container is empty currently"), - DEFINE_API_OUTCOME(IS_NEW, -8, "The item is new"), - DEFINE_API_OUTCOME(EXISTING, -9, "The item was already present"), - DEFINE_API_OUTCOME(FAILURE, -10, "A failure has occurred"), - DEFINE_API_OUTCOME(OUT_OF_MEMORY, -11, "There is not enough memory for the " - "request according to the operating system"), - DEFINE_API_OUTCOME(ACCESS_DENIED, -12, "The request was denied, possibly " - "by the operating system"), - DEFINE_API_OUTCOME(IN_USE, -13, "The object is already in exclusive use"), - DEFINE_API_OUTCOME(UNINITIALIZED, -14, "The object has not been " - "constructed properly"), - DEFINE_API_OUTCOME(TIMED_OUT, -15, "The allowed time has now elapsed"), - DEFINE_API_OUTCOME(GARBAGE, -16, "The request or response has been " - "corrupted"), - DEFINE_API_OUTCOME(NO_SPACE, -17, "A programmatic limit on storage space " - "has been reached"), - DEFINE_API_OUTCOME(DISALLOWED, -18, "The method denied the request"), - DEFINE_API_OUTCOME(INCOMPLETE, -19, "The operation did not finish or the " - "object is not completed"), - DEFINE_API_OUTCOME(NO_HANDLER, -20, "The object type passed in was not " - "understood by the invoked method"), - DEFINE_API_OUTCOME(NONE_READY, -21, "There were no objects available"), - DEFINE_API_OUTCOME(INVALID, -22, "That request or object was invalid"), - DEFINE_API_OUTCOME(PARTIAL, -23, "The request was only partially finished"), - DEFINE_API_OUTCOME(NO_LICENSE, -24, "The software license does not permit" - "this request"), - DEFINE_API_OUTCOME(UNEXPECTED, -25, "This item was unexpected, although " - "not necessarily erroneous"), - DEFINE_API_OUTCOME(ENCRYPTION_MISMATCH, -26, "The request failed due to a " - "mismatch between encryption expected and encryption provided") - }; - - static const char *outcome_name(const outcome &to_name); - //!< Returns a string representation of the outcome "to_name". - /*!< If "to_name" is unknown, because it's not a member of the - common::outcomes enum, then a string reporting that is returned. */ - -}; //class. - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/basis/contracts.h b/core/library/basis/contracts.h deleted file mode 100644 index def88b3e..00000000 --- a/core/library/basis/contracts.h +++ /dev/null @@ -1,188 +0,0 @@ -#ifndef CONTRACTS_GROUP -#define CONTRACTS_GROUP - -/*****************************************************************************\ -* * -* Name : contracts * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -/*! @file contracts.h - This is a collection of fairly vital interface classes. -*/ - -#include "outcome.h" - -namespace basis { - -// forward declarations. -class base_string; - -////////////// - -//! Defines an attribute base class that supports get and set operations. - -class attribute : public virtual root_object -{ -public: - virtual const root_object &get() const = 0; - virtual void set(const root_object &new_value) = 0; -}; - -////////////// - -//! Base class for object that can tell itself apart from other instances. -class equalizable : public virtual root_object -{ -public: - virtual bool equal_to(const equalizable &s2) const = 0; - //! the virtual method for object equality. - virtual bool operator == (const equalizable &s2) const { return equal_to(s2); } - //! synactic sugar for comparison operators. -}; - -////////////// - -//! A base for objects that can be alphabetically (lexicographically) ordered. - -class orderable : public virtual equalizable -{ -public: - virtual bool less_than(const orderable &s2) const = 0; - //! the virtual method for object ordering. - virtual bool operator < (const orderable &s2) const { return less_than(s2); } - //! synactic sugar for comparison operators. -}; - -////////////// - -//! Provides an abstract base for logging mechanisms. - -class base_logger : public virtual root_object -{ -public: - virtual outcome log(const base_string &info, int filter) = 0; - //!< writes the information in "info" to the logger using the "filter". - /*!< the derived class can interpret the filter appropriately and only - show the "info" if the filter is enabled. */ -}; - -////////////// - -//! Macro for defining a logging filter value. -#define DEFINE_FILTER(NAME, CURRENT_VALUE, INFO_STRING) NAME = CURRENT_VALUE - -//! These filter values are the most basic, and need to be known everywhere. -enum root_logging_filters { - DEFINE_FILTER(NEVER_PRINT, -1, "This diagnostic entry should be dropped and never seen"), - DEFINE_FILTER(ALWAYS_PRINT, 0, "This diagnostic entry will always be shown or recorded") -}; - -////////////// - -//! Interface for a simple form of synchronization. -/*! - Derived classes must provide a locking operation and a corresponding - unlocking operation. -*/ - -class base_synchronizer : public virtual root_object -{ -public: - virtual void establish_lock() = 0; - virtual void repeal_lock() = 0; -}; - -////////////// - -//! A clonable object knows how to make copy of itself. - -class clonable : public virtual root_object -{ -public: - virtual clonable *clone() const = 0; -}; - -////////////// - -//! Root object for any class that knows its own name. -/*! - This is a really vital thing for debugging to be very helpful, and unfortunately it's not - provided by C++. -*/ - -class nameable : public virtual root_object -{ -public: - virtual const char *class_name() const = 0; - //!< Returns the bare name of this class as a constant character pointer. - /*!< The name returned here is supposed to be just a class name and not - provide any more information than that. It is especially important not to - add any syntactic elements like '::' to the name, since a bare alphanumeric - name is expected. */ -}; - -////////////// - -//! A base class for objects that can provide a synopsis of their current state. -/*! - This helps a lot during debugging and possibly even during normal runtime, since it causes - the object to divulge its internal state for viewing in hopefully readable text. -*/ - -class text_formable : public virtual nameable -{ -public: - virtual const char *class_name() const = 0; // forwarded requirement from nameable. - - virtual void text_form(base_string &state_fill) const = 0; - //!< Provides a text view of all the important info owned by this object. - /*!< It is understood that there could be a large amount of information and that this - function might take a relatively long time to complete. */ -}; - -////////////// - -//! the base class of the most easily used and tested objects in the library. -/*! - Each hoople_standard object must know its name, how to print out its data members, and whether - it's the same as another object or not. -*/ - -class hoople_standard : public virtual text_formable, public virtual equalizable -{ -public: - // this is a union class and has no extra behavior beyond its bases. -}; - -////////////// - -//! a base for classes that can stream their contents out to a textual form. - -class text_streamable : public virtual nameable -{ -public: - virtual bool produce(base_string &target) const = 0; - //!< sends the derived class's member data into the "target" in a reversible manner. - /*!< this should use a tagging system of some sort so that not only can the derived class - verify that its type is really right there in the string, but also that it gets all of its - class data and no other data. the "target" will be destructively consumed, and after a - successful call will no longer contain the object's streamed form at its head. */ - virtual bool consume(const base_string &source) = 0; - //!< chows down on a string that supposedly contains a streamed form. - /*!< the derived class must know how to eat just the portion of the string that holds - its data type and no more. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/basis/definitions.h b/core/library/basis/definitions.h deleted file mode 100644 index c76e10ae..00000000 --- a/core/library/basis/definitions.h +++ /dev/null @@ -1,193 +0,0 @@ -#ifndef DEFINITIONS_GROUP -#define DEFINITIONS_GROUP - -/*****************************************************************************\ -* * -* Name : definitions * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! @file "definitions.h" Constants and objects used throughout HOOPLE. -/*! @file - Defines a set of useful universal constants (for our chosen universe) and - a set of aliases for convenient abstract and concrete data types. - This is the lowest-level header in hoople and should not include any others. -*/ - -namespace basis { - -////////////// - -// Constants... - -//! The value representing a pointer to nothing, or nothing itself. -#define NIL 0 - -//! A fundamental constant measuring the number of bits in a byte. -#define BITS_PER_BYTE 8 - -//! An approximation of the fundamental circular constant. -#define PI_APPROX 3.14159265358 - -////////////// - -// Data Structures & Functions - -//! This macro just eats what it's passed; it marks unused formal parameters. -#define formal(parameter) - -//! A fairly important unit which is seldom defined... -typedef unsigned char abyte; -/* ridiculous! all to shut microsoft up about ambiguous byte definitions, -which seems like a bug. -struct byte { - byte(unsigned char b = 0) : c_data(b) {} -// byte(char b) : c_data(b) {} - operator unsigned char() const { return c_data; } - operator char() const { return c_data; } - operator int() const { return c_data; } - operator unsigned int() const { return c_data; } - operator bool() const { return (bool)c_data; } - byte operator &(byte and_with) { return c_data & and_with; } - byte operator |(byte or_with) { return c_data & or_with; } - unsigned char c_data; -}; -*/ - -#if defined(UNICODE) && defined(__WIN32__) - //! the flexichar type is always appropriate to hold data for win32 calls. - typedef wchar_t flexichar; -#else - // this version simply defangs any conversions. - typedef char flexichar; -#endif - -//! Abbreviated name for unsigned integers. -typedef unsigned int un_int; -//! Abbreviated name for unsigned short integers. -typedef unsigned short un_short; -//! Abbreviated name for unsigned long integers. -typedef unsigned long un_long; - -// some maximum and minimum values that are helpful. -#ifndef MAXINT32 - //! Maximum 32-bit integer value. - #define MAXINT32 0x7fffffff -#endif -#ifndef MININT32 - //! Minimum 32-bit integer value. - #define MININT32 0x80000000 -#endif -#ifndef MAXINT16 - //! Maximum 32-bit integer value. - #define MAXINT16 0x7fff -#endif -#ifndef MININT16 - //! Minimum 32-bit integer value. - #define MININT16 0x8000 -#endif -#ifndef MAXCHAR - //! Maximum byte-based character value. - #define MAXCHAR 0x7f -#endif -#ifndef MINCHAR - //! Minimum byte-based character value. - #define MINCHAR 0x80 -#endif -#ifndef MAXBYTE - //! Maximum unsigned byte value. - #define MAXBYTE 0xff -#endif -#ifndef MINBYTE - //! Minimum unsigned byte value. - #define MINBYTE 0x00 -#endif - -// Provide definitions for integers with platform independent specific sizes. -// Note that these may have to be adjusted for 64 bit platforms. -typedef char int8; -typedef unsigned char uint8; -typedef signed short int16; -typedef unsigned short uint16; -typedef signed int int32; -typedef unsigned int uint32; - -////////////// - -// useful time constants. - -// the _ms suffix indicates that these are measured in milliseconds. -const int SECOND_ms = 1000; //!< Number of milliseconds in a second. -const int MINUTE_ms = 60 * SECOND_ms; //!< Number of milliseconds in a minute. -const int HOUR_ms = 60 * MINUTE_ms; //!< Number of milliseconds in an hour. -const int DAY_ms = 24 * HOUR_ms; //!< Number of milliseconds in a day. - -// the _s suffix indicates that these are measured in seconds. -const int MINUTE_s = 60; //!< Number of seconds in a minute. -const int HOUR_s = 60 * MINUTE_s; //!< Number of seconds in an hour. -const int DAY_s = 24 * HOUR_s; //!< Number of seconds in a day. - -////////////// - -// useful general constants. - -const int KILOBYTE = 1024; //!< Number of bytes in a kilobyte. -const int MEGABYTE = KILOBYTE * KILOBYTE; //!< Number of bytes in a megabyte. -const int GIGABYTE = MEGABYTE * KILOBYTE; //!< Number of bytes in a gigabyte. -const double TERABYTE = double(GIGABYTE) * double(KILOBYTE); -//double TERABYTE() { return double(GIGABYTE) * double(KILOBYTE); } - //!< Number of bytes in a terabyte. -// /*!< Implemented as a function to avoid annoying link errors for double -// floating point constants in some compilers. */ - -////////////// - -// Super basic objects... - -//! lowest level object for all hoople objects. supports run-time type id. - -class root_object -{ -public: - virtual ~root_object() {} -}; - -////////////// - -// compiler specific dumping ground for global settings... - -#ifdef _MSC_VER - // turns off annoying complaints from visual c++. - #pragma warning(disable : 4251 4275 4003 4800 4355 4786 4290 4996 4407) - #pragma warning(error : 4172) - // 4251 and 4275 turn off warnings regarding statically linked code - // not being marked with dll import/export flags. - // 4003 turns off warnings about insufficient number of parameters passed - // to a macro. - // 4800 turns off the warning about conversion from int to bool not being - // efficient. - // 4355 turns off the warning re 'this' used in base member init list. - // 4786 turns off the warning about 'identifier' truncated to 'number' - // characters in the debug information which frequenly happens when - // STL pair and set templates are expanded. - // 4172 is made an error because this warning is emitted for a dangerous - // condition; the address of a local variable is being returned, making - // the returned object junk in almost all cases. - // 4996 turns off warnings about deprecated functions, which are mostly - // bullshit, since these are mainly the core posix functions. -#endif // ms visual c++. - -////////////// - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/basis/enhance_cpp.h b/core/library/basis/enhance_cpp.h deleted file mode 100644 index 98589ec7..00000000 --- a/core/library/basis/enhance_cpp.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef ENHANCE_CPP_GROUP -#define ENHANCE_CPP_GROUP - -/*****************************************************************************\ -* * -* Name : enhance_cpp * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace basis { - -//! Provides missing language features in C++. -/*! - The most noticeable missing thing in C++ when trying to build debugging - and tracking subsystems with it is *reflection*. This header attempts to - ameliorate some of the worst missing parts, such as the fact that a function - cannot get its own name, and other really helpful features. -*/ - -//hmmm: temporary to hide missing code. -#define frame_tracking_instance -#define __trail_of_function(a, b, c, d, e) - -class enhance_cpp : public virtual root_object -{ -public: - // this class is an encapsulator; any macros are not actual members. - -////////////// - - //! Defines the name of a class by providing a couple standard methods. - /*! This provides a virtual function functionality slice for the class name, - as well as a static version that can be used when no instances of the - class exist yet. */ - #define DEFINE_CLASS_NAME(objname) \ - static const char *static_class_name() { return (objname); } \ - virtual const char *class_name() const { return static_class_name(); } - -////////////// - - //! FUNCDEF sets the name of a function (and plugs it into the callstack). - /*! This macro establishes the function name and should be used at the top - of functions that wish to participate in class based logged as well as the - callstack tracing capability of hoople. A new variable is created on the - stack to track the function's presence until the function exits, at which - time the stack will no longer show it as active. */ - #define FUNCDEF(func_in) \ - const char *func = (const char *)func_in; \ - frame_tracking_instance __trail_of_function(static_class_name(), func, \ - __FILE__, __LINE__, true) - -////////////// - - //! A macro used within the FUNCTION macro to do most of the work. - #define BASE_FUNCTION(func) astring just_function = astring(func); \ - astring function_name = static_class_name(); \ - function_name += astring("::") + just_function - //! This macro sets up a descriptive variable called "function_name". - /*! The variable includes the object's name (static_class_name() must be - implemented for the current object) and the current function's name within - that object (the macro "func" must be defined with that name). */ - #define FUNCTION(func) BASE_FUNCTION(func); \ - function_name += ": "; \ - update_current_stack_frame_line_number(__LINE__) - - //! A macro used within the INSTANCE_FUNCTION macro. - #define BASE_INSTANCE_FUNCTION(func) astring just_function = astring(func); \ - astring function_name = instance_name(); \ - function_name += astring("::") + just_function - //! A function macro that contains more information. - /*! This macro is similar to FUNCTION but it uses the class's instance_name() - method (see root_object). The instance function usually will provide more - information about the class. */ - #define INSTANCE_FUNCTION(func) BASE_INSTANCE_FUNCTION(func); \ - function_name += ": "; \ - update_current_stack_frame_line_number(__LINE__) - -////////////// - - //! __WHERE__ is a macro that combines the file and line number macros. - /*! These are available to most compilers as automatically updated macros - called __FILE__ and __LINE__. This macro can be used anywhere an astring can - be used and reports the current file name and line number. */ - #define __WHERE__ basis::a_sprintf("%s [line %d]", __FILE__, __LINE__) - -}; - -} //namespace. - -#endif - diff --git a/core/library/basis/environment.cpp b/core/library/basis/environment.cpp deleted file mode 100644 index 5e50abfb..00000000 --- a/core/library/basis/environment.cpp +++ /dev/null @@ -1,87 +0,0 @@ -////////////// -// Name : environment -// Author : Chris Koeritz -////////////// -// Copyright (c) 1994-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "environment.h" - -#include -#include -#ifdef __UNIX__ - #include - #include -#endif -#ifdef __WIN32__ - #define _WINSOCKAPI_ // make windows.h happy about winsock. - #include - #include -#endif - -namespace basis { - -astring environment::get(const astring &variable_name) -{ -#ifdef __WIN32__ - char *value = getenv(variable_name.upper().observe()); - // dos & os/2 require upper case for the name, so we just do it that way. -#else - char *value = getenv(variable_name.observe()); - // reasonable OSes support mixed-case environment variables. -#endif - astring to_return; - if (value) - to_return = astring(value); - return to_return; -} - -bool environment::set(const astring &variable_name, const astring &value) -{ - int ret = 0; -#ifdef __WIN32__ - astring assignment = variable_name + "=" + value; - ret = _putenv(assignment.s()); -#else - ret = setenv(variable_name.s(), value.s(), true); -#endif - return !ret; -} - -basis::un_int environment::system_uptime() -{ -#ifdef __WIN32__ - return timeGetTime(); -#else - static clock_t __ctps = sysconf(_SC_CLK_TCK); // clock ticks per second. - static const double __multiplier = 1000.0 / double(__ctps); - // the multiplier gives us our full range for the tick counter. - - // read uptime info from the OS. - tms uptime; - basis::un_int real_ticks = times(&uptime); - - // now turn this into the number of milliseconds. - double ticks_up = (double)real_ticks; - ticks_up = ticks_up * __multiplier; // convert to time here. - - // we use the previous version of this calculation, which expected a basis::u_int - // to double conversion to provide a modulo operation rather than just leaving - // the basis::un_int at its maximum value (2^32-1). however, that expectation is not - // guaranteed on some platforms (e.g., ARM processor with floating point - // emulation) and thus it becomes a bug around 49 days and 17 hours into - // OS uptime because the value gets stuck at 2^32-1 and never rolls over. - return basis::un_int(ticks_up); -#endif -} - -} //namespace. - diff --git a/core/library/basis/environment.h b/core/library/basis/environment.h deleted file mode 100644 index e9823e73..00000000 --- a/core/library/basis/environment.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef ENVIRONMENT_CLASS -#define ENVIRONMENT_CLASS - -////////////// -// Name : environment -// Author : Chris Koeritz -////////////// -// Copyright (c) 1994-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "astring.h" -#include "definitions.h" - -namespace basis { - -//! Provides access to the system's environment variables. - -class environment : public virtual root_object -{ -public: - static astring get(const astring &variable_name); - //!< looks up the "variable_name" in the current environment variables. - /*!< this returns the value for "variable_name" as it was found in the - operating system's environment variables that are defined at this point - in time for the user and process. the returned string will be empty if no - variable under that name could be found. */ - -// static astring get(const char *variable_name) { return get(astring(variable_name)); } - //!< synonym using simpler char pointer. - - static bool set(const astring &variable_name, const astring &value); - //!< adds or creates "variable_name" in the environment. - /*!< changes the current set of environment variables by adding or - modifying the "variable_name". its new value will be "value". */ - -// static bool set(const char *variable_name, const char *value) -// { return set(astring(variable_name), astring(value)); } - //!< synonym using simpler char pointers. - - static basis::un_int system_uptime(); - //!< gives the operating system's uptime in a small form that rolls over. -}; - -} //namespace. - -#endif - diff --git a/core/library/basis/functions.h b/core/library/basis/functions.h deleted file mode 100644 index 777461a9..00000000 --- a/core/library/basis/functions.h +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef FUNCTIONS_GROUP -#define FUNCTIONS_GROUP - -/*****************************************************************************\ -* * -* Name : functions * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -/*! @file functions.h - Provides a set of useful mini-functions. -*/ - -#include "definitions.h" - -namespace basis { - -template type maximum(type a, type b) - { return (a > b)? a : b; } - //!< minimum returns the lesser of two values. -template type minimum(type a, type b) - { return (a < b)? a : b; } - //!< maximum returns the greater of two values. - -template type absolute_value(type a) - { return (a >= 0)? a : -a; } - //!< Returns a if a is non-negative, and returns -a otherwise. - -////////////// - -template bool positive(const type &a) { return a > 0; } - //!< positive returns true if "a" is greater than zero, or false otherwise. -template bool non_positive(const type a) { return a <= 0; } - //!< non_positive returns true if "a" is less than or equal to zero. -template bool negative(const type &a) { return a < 0; } - //!< negative returns true if "a" is less than zero. -template bool non_negative(const type &a) { return a >= 0; } - //!< non_negative returns true if "a" is greater than or equal to zero. - -////////////// - -// the following comparisons are borrowed from the STL. they provide the full set of comparison -// operators for any object that implements the equalizable and orderable base classes. -template -bool operator != (const T1 &x, const T2 &y) { return !(x == y); } - -template -bool operator > (const T1 &x, const T2 &y) { return y < x; } - -template -bool operator <= (const T1 &x, const T2 &y) { return !(y < x); } - -template -bool operator >= (const T1 &x, const T2 &y) { return !(x < y); } - -////////////// - -//! dynamically converts a type to a target type, or throws an exception if it cannot. -template -target_type *cast_or_throw(source_type &to_cast, const target_type &ignored) -{ - if (!&ignored) {} // do nothing. - target_type *cast = dynamic_cast(&to_cast); - if (!cast) throw "error: casting problem, unknown RTTI cast."; - return cast; -} - -//! const version of the cast_or_throw template. -template -const target_type *cast_or_throw(const source_type &to_cast, const target_type &ignored) -{ - if (!&ignored) {} // do nothing. - const target_type *cast = dynamic_cast(&to_cast); - if (!cast) throw "error: casting problem, unknown RTTI cast."; - return cast; -} - -////////////// - -template bool range_check(const type &c, const type &low, - const type &high) { return (c >= low) && (c <= high); } - //!< Returns true if "c" is between "low" and "high" inclusive. - -template type square(const type &a) { return a * a; } - //!< Returns the square of the object (which is a * a). - -template void flip_increasing(type &a, type &b) - { if (b < a) { type tmp = a; a = b; b = tmp; } } - //!< Makes sure that two values are in increasing order (a < b). - -template void flip_decreasing(type &a, type &b) - { if (b > a) { type tmp = a; a = b; b = tmp; } } - //!< Makes sure that two values are in decreasing order (a > b). - -template void swap_values(type &a, type &b) - { type tmp = a; a = b; b = tmp; } - //!< Exchanges the values held by "a" & "b". - -template type sign(type a) - { if (a < 0) return -1; else if (a > 0) return 1; else return 0; } - //!< Returns the numerical sign of a number "a". - -////////////// - -// helpful coding / debugging macros: - -//! deletion with clearing of the pointer. -/*! this function simplifies the two step process of deleting a pointer and -then clearing it to NIL. this makes debugging a bit easier since an access -of NIL should always cause a fault, rather than looking like a possibly -valid object. */ -template -void WHACK(contents * &ptr) { if (ptr) { delete ptr; ptr = NIL; } } - -//! Returns an object that is defined statically. -/*! Thus the returned object will never be recreated once this function -is called within the same scope of memory (within a dynamic library or -application). This is useful for templates that want to have access to a -bogus element whose contents don't matter. NOTE: bogonic is not -thread safe! */ -template type &bogonic() { - static type local_bogon; - return local_bogon; -} - -////////////// - -template -type number_of_packets(type message_size, type packet_size) -{ return message_size / packet_size + ((message_size % packet_size) != 0); } - //!< Reports number of packets needed given a total size and the packet size. - /*!< This returns the number of packets needed to contain a contiguous array - of characters with size "message_size" when the number of characters - per packet is "packet_size". */ - -template -type last_packet_size(type message_size, type packet_size) -{ return message_size % packet_size? message_size % packet_size : packet_size; } - //!< Tells how many bytes are used within last packet. - /*< The companion call to number_of_packets; it returns the size of the last - packet in the sequence of packets, taking into account the special case - where the message_size divides evenly. */ - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/basis/gnu_header.h b/core/library/basis/gnu_header.h deleted file mode 100644 index d78b2a4f..00000000 --- a/core/library/basis/gnu_header.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef {NAME}_CLASS -#define {NAME}_CLASS - -////////////// -// Name : {class name} -// Author : {your name} -////////////// -// Copyright (c) 2010-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -//! brief description goes here. -/*! - detailed description goes here. -*/ - -////////////// -// class definition goes here....... -////////////// - -#endif - diff --git a/core/library/basis/guards.cpp b/core/library/basis/guards.cpp deleted file mode 100644 index 5bc81378..00000000 --- a/core/library/basis/guards.cpp +++ /dev/null @@ -1,47 +0,0 @@ -////////////// -// Name : guards -// Author : Chris Koeritz -////////////// -// Copyright (c) 1989-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "astring.h" -#include "guards.h" - -namespace basis { - -void format_error(const base_string &class_name, const base_string &func_name, - const base_string &error_message, base_string &to_fill) -{ - astring to_return = class_name; - to_return += "::"; - to_return += func_name; - to_return += ": "; - to_return += error_message; - to_fill = to_return; -} - -void throw_error(const base_string &class_name, const base_string &func_name, - const base_string &error_message) -{ - astring to_throw; - format_error(class_name, func_name, error_message, to_throw); - throw to_throw; -} - -void throw_error(const astring &class_name, const astring &func_name, - const astring &error_message) -{ - throw_error((base_string &)class_name, (base_string &)func_name, (base_string &)error_message); -} - -} //namespace. - diff --git a/core/library/basis/guards.h b/core/library/basis/guards.h deleted file mode 100644 index 0f24a908..00000000 --- a/core/library/basis/guards.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef GUARDS_GROUP -#define GUARDS_GROUP - -////////////// -// Name : guards -// Author : Chris Koeritz -////////////// -// Copyright (c) 1989-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -//! The guards collection helps in testing preconditions and reporting errors. -/*! - It also provides checking of boundary conditions, macros for causing - immediate program exit, and other sentinels for constructing preconditions - and postconditions. -*/ - -namespace basis { - -// forward declaration. -class astring; -class base_string; - -////////////// - -// simpler guards first... - -//! Returns true if the value is within the range specified. -template -bool in_range(const contents &value, const contents &low, const contents &high) -{ return !( (high < low) || (value < low) || (value > high) ); } - -////////////// - -//! Verifies that "value" is between "low" and "high", inclusive. -/*! When the number is not in bounds, the function that is currently executing -returns the "to_return" default provided. "to_return" can be empty for -functions that return void. Note: it is also considered a failure for high to -be less than low. */ -#define bounds_return(value, low, high, to_return) \ - { if (!basis::in_range(value, low, high)) return to_return; } - -////////////// - -//! Verifies that "value" is between "low" and "high", inclusive. -/*! "Value" must be an object for which greater than and less than are defined. -The static_class_name() method and func definition are used to tag the -complaint that is emitted when problems are detected. Note that if -CATCH_ERRORS is defined, then the program is _halted_ if the value is out -of bounds. Otherwise, the "to_return" value is returned. */ -#ifdef CATCH_ERRORS - #define bounds_halt(value, low, high, to_return) { \ - if (((value) < (low)) || ((value) > (high))) { \ - throw_error(basis::astring(static_class_name()), basis::astring(func), \ - basis::astring("value ") + #value \ - + " was not in range " + #low + " to " + #high \ - + " at " + __WHERE__); \ - return to_return; \ - } \ - } -#else - #define bounds_halt(a, b, c, d) bounds_return(a, b, c, d) -#endif - -////////////// - -//! writes a string "to_fill" in a nicely formatted manner using the class and function names. -void format_error(const base_string &class_name, const base_string &func_name, - const base_string &error_message, base_string &to_fill); - -//! throws an error that incorporates the class name and function name. -void throw_error(const base_string &class_name, const base_string &func_name, - const base_string &error_message); - -//! synonym method using astrings for easier char * handling. -void throw_error(const astring &class_name, const astring &func_name, - const astring &error_message); - -////////////// - -} // namespace. - -#endif - diff --git a/core/library/basis/makefile b/core/library/basis/makefile deleted file mode 100644 index fce7c926..00000000 --- a/core/library/basis/makefile +++ /dev/null @@ -1,10 +0,0 @@ -include cpp/variables.def - -PROJECT = basis -TYPE = library -SOURCE = astring.cpp common_outcomes.cpp utf_conversion.cpp environment.cpp guards.cpp \ - mutex.cpp -TARGETS = basis.lib - -include cpp/rules.def - diff --git a/core/library/basis/mutex.cpp b/core/library/basis/mutex.cpp deleted file mode 100644 index 0bf8b943..00000000 --- a/core/library/basis/mutex.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/*****************************************************************************\ -* * -* Name : mutex * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// NOTE: we are explicitly avoiding use of new and delete here because this -// class is needed by our memory allocation object, which would be -// providing the new and delete methods. - -#include "mutex.h" - -#include - -#ifdef __UNIX__ - #include -#endif -#ifdef __WIN32__ - #define _WINSOCKAPI_ // make windows.h happy about winsock. - #include -#endif - -namespace basis { - -mutex::mutex() { construct(); } - -mutex::~mutex() { destruct(); } - -void mutex::establish_lock() { lock(); } - -void mutex::repeal_lock() { unlock(); } - -void mutex::construct() -{ -#ifdef __WIN32__ - c_os_mutex = (CRITICAL_SECTION *)malloc(sizeof(CRITICAL_SECTION)); - InitializeCriticalSection((LPCRITICAL_SECTION)c_os_mutex); -#elif defined(__UNIX__) - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - int ret = -1; -#ifdef __APPLE__ - ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); -#else - ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); -#endif - if (ret != 0) { -//printf("failed to initialize mutex attributes!\n"); fflush(NIL); - } - c_os_mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); - pthread_mutex_init((pthread_mutex_t *)c_os_mutex, &attr); - pthread_mutexattr_destroy(&attr); -#else - #pragma error("no implementation of mutexes for this OS yet!") -#endif -} - -void mutex::destruct() -{ - defang(); -} - -void mutex::defang() -{ - if (!c_os_mutex) return; // already defunct. -#ifdef __WIN32__ - DeleteCriticalSection((LPCRITICAL_SECTION)c_os_mutex); - free(c_os_mutex); -#elif defined(__UNIX__) - pthread_mutex_destroy((pthread_mutex_t *)c_os_mutex); - free(c_os_mutex); -#else - #pragma error("no implementation of mutexes for this OS yet!") -#endif - c_os_mutex = 0; -} - -void mutex::lock() -{ - if (!c_os_mutex) return; -#ifdef __WIN32__ - EnterCriticalSection((LPCRITICAL_SECTION)c_os_mutex); -#elif defined(__UNIX__) - pthread_mutex_lock((pthread_mutex_t *)c_os_mutex); -#else - #pragma error("no implementation of mutexes for this OS yet!") -#endif -} - -void mutex::unlock() -{ - if (!c_os_mutex) return; -#ifdef __WIN32__ - LeaveCriticalSection((LPCRITICAL_SECTION)c_os_mutex); -#elif defined(__UNIX__) - pthread_mutex_unlock((pthread_mutex_t *)c_os_mutex); -#else - #pragma error("no implementation of mutexes for this OS yet!") -#endif -} - -} //namespace. - diff --git a/core/library/basis/mutex.h b/core/library/basis/mutex.h deleted file mode 100644 index 1efa08e3..00000000 --- a/core/library/basis/mutex.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef MUTEX_CLASS -#define MUTEX_CLASS - -/*****************************************************************************\ -* * -* Name : mutex * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "contracts.h" - -//! A simple primitive class that encapsulates OS support for mutual exclusion. -/*! - The word "mutex" is an abbreviation for "mutual exclusion". The mutex - provides a simple synchronization object that supports the programming of - critical sections. It is guaranteed to be safe for threads, but it is only - useful within one application rather than between multiple applications. - The mutex_base is hardly ever used directly; instead the mutex class should - be used. -*/ - -namespace basis { - -class mutex : public virtual base_synchronizer -{ -public: - mutex(); //!< Constructs a new mutex. - - virtual ~mutex(); - //!< Destroys the mutex. It should not be locked upon destruction. - - //! Constructor for use with malloc/free instead of new/delete. - void construct(); - - //! Destructor for use with malloc/free instead of new/delete. - void destruct(); - - void lock(); - //!< Clamps down on the mutex, if possible. - /*!< Otherwise the current thread is blocked until the mutex is unlocked. */ - - void unlock(); - //!< Gives up the possession of the mutex. - - virtual void establish_lock(); - //!< Satisfies base class requirements for locking. - virtual void repeal_lock(); - //!< Satisfies base class requirements for unlocking. - -private: - void *c_os_mutex; //!< OS version of the mutex. - - void defang(); - //!< Removes the underlying OS synchronization primitive. - /*!< This method renders this mutex object inoperable. This is useful - when the reason for the lock has vanished, but the mutex object cannot be - deleted yet. Sometimes it may still be referred to, but there is no - longer any critical section to be protected. */ - - mutex(const mutex &); //!< not allowed. - mutex &operator =(const mutex &); //!< not allowed. -}; - -////////////// - -//! auto_synchronizer simplifies concurrent code by automatically unlocking. -/*! - This relies on the base_synchronizer for the form of the objects that - will provide synchronization. The synchronization object is locked when the - auto_synchronizer is created and it is unlocked when the auto_synchronizer - is destroyed. This is most useful when the auto_synchronizer is an automatic - object; the synchronization lock is grabbed at the point of creation (even - in the middle of a function) and it is released when the function or block - scope exits, thus freeing us of the responsibility of always unlocking the - object before exiting from the critical section. - - More Detail: - The auto_synchronizer provides an easy way to provide nearly idiot-proof - synchronization of functions that share the same locking object. By giving - the synchronizer a working object that's derived from synchronization_base, - its mere construction establishes the lock and its destruction releases the - lock. Thus you can protect a critical section in a function by creating - the auto_synchronizer at the top of the function as an automatic object, or - wherever in the function body is appropriate. When the function exits, the - auto_synchronizer will be destroyed as part of the cleanup and the lock will - be released. If there are multiple synchronization objects in a function, - then be very careful. One must order them appropriately to avoid a deadlock. - - for example: @code - mutex my_lock; // the real synchronization primitive. - ... // lots of program in between. - int calculate_average() { - // our function that must control thread concurrency. - auto_synchronizer syncho(my_lock); // establishes the lock. - ... // lots of stuff done in the function in safety from other threads. - } // end of the function. @endcode - - Note that there was no unlock of the mutex above. Remembering to unlock - synchronization primitives is one of the most troublesome requirements of - programming with multiple threads; the auto_synchronizer can be used in - many situations to automate the release of the lock. -*/ - -class auto_synchronizer -{ -public: - auto_synchronizer(base_synchronizer &locker) : _locker(locker) - { _locker.establish_lock(); } - //!< Construction locks the "locker" object for the current program scope. - /*!< This automatically locks a synchronization object until the current - scope (such as a function or even just a block) is exited, which implements - synchronization without needing multiple unlock calls before every return - statement. */ - - ~auto_synchronizer() { _locker.repeal_lock(); } - //!< Releases the lock as this object goes out of scope. - -private: - base_synchronizer &_locker; //!< the locking object. - - // disallowed. - auto_synchronizer(const auto_synchronizer &locker); - auto_synchronizer &operator =(const auto_synchronizer &locker); -}; - -} //namespace. - -#endif - diff --git a/core/library/basis/outcome.h b/core/library/basis/outcome.h deleted file mode 100644 index 830ef4d3..00000000 --- a/core/library/basis/outcome.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef OUTCOME_CLASS -#define OUTCOME_CLASS - -/*****************************************************************************\ -* * -* Name : outcome * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "definitions.h" - -namespace basis { - -//! Outcomes describe the state of completion for an operation. -/*! - These are an extremely simple representation of a non-exceptional exit - value as an integer. The range of expression is from 0 to MAXINT. - Outcomes are meant to represent the category of 'how' the operation - completed; they do not carry along any results of 'what' was produced. -*/ - -class outcome -{ -public: - outcome(int value = 0) : c_outcome_value(value) {} - //!< Represents the completion of an operation as a particular "value". - /*!< The outcomes partition the input space of the operation and represent - the different conclusions possible during processing. Values for outcomes - must be maintained on a system-wide basis as unique identifiers for them - to be meaningful. Note that zero is reserved as a default outcome value. - This usually translates to an outcome of OKAY. */ - - ~outcome() { c_outcome_value = 0; } //!< destructor resets outcome value. - - bool equal_to(const outcome &to_compare) const - { return c_outcome_value == to_compare.c_outcome_value; } - //!< Returns true if this outcome is equal to "to_compare". - /*!< comparisons between outcomes will operate properly provided that - all system-wide outcome values are unique. */ - - bool operator == (int to_compare) const - { return c_outcome_value == to_compare; } - //!< Returns true if this outcome is equal to the integer "to_compare". - - int value() const { return c_outcome_value; } - // -#include -#ifdef CVTUTF_DEBUG - #include -#endif - -namespace basis { - -static const int halfShift = 10; /* used for shifting by 10 bits */ - -static const UTF32 halfBase = 0x0010000UL; -static const UTF32 halfMask = 0x3FFUL; - -#define UNI_SUR_HIGH_START (UTF32)0xD800 -#define UNI_SUR_HIGH_END (UTF32)0xDBFF -#define UNI_SUR_LOW_START (UTF32)0xDC00 -#define UNI_SUR_LOW_END (UTF32)0xDFFF - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - if (target >= targetEnd) { - result = targetExhausted; break; - } - ch = *source++; - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_LEGAL_UTF32) { - if (flags == strictConversion) { - result = sourceIllegal; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - --source; /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF32* target = *targetStart; - UTF32 ch, ch2; - while (source < sourceEnd) { - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - if (target >= targetEnd) { - source = oldSource; /* Back up source pointer! */ - result = targetExhausted; break; - } - *target++ = ch; - } - *sourceStart = source; - *targetStart = target; -#ifdef CVTUTF_DEBUG -if (result == sourceIllegal) { - fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); - fflush(stderr); -} -#endif - return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Index into the table below with the first byte of a UTF-8 sequence to - * get the number of trailing bytes that are supposed to follow it. - * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is - * left as-is for anyone who may want to do such conversion, which was - * allowed in earlier algorithms. - */ -static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 -}; - -/* - * Magic values subtracted from a buffer value during UTF8 conversion. - * This table contains as many values as there might be trailing bytes - * in a UTF-8 sequence. - */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; - -/* - * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed - * into the first byte, depending on how many bytes follow. There are - * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... etc.). Remember that sequencs - * for *legal* UTF-8 will be 4 or fewer bytes total. - */ -static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - -/* --------------------------------------------------------------------- */ - -/* The interface converts a whole buffer to avoid function-call overhead. - * Constants have been gathered. Loops & conditionals have been removed as - * much as possible for efficiency, in favor of drop-through switches. - * (See "Note A" at the bottom of the file for equivalent code.) - * If your compiler supports it, the "isLegalUTF8" call can be turned - * into an inline function. - */ - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - UTF32 ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - } - - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Utility routine to tell whether a sequence of bytes is legal UTF-8. - * This must be called with the length pre-determined by the first byte. - * If not calling this from ConvertUTF8to*, then the length can be set by: - * length = trailingBytesForUTF8[*source]+1; - * and the sequence is illegal right away if there aren't that many bytes - * available. - * If presented with a length > 4, this returns false. The Unicode - * definition of UTF-8 goes up to 4-byte sequences. - */ - -static Booleano isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xED: if (a > 0x9F) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } - - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - } - if (*source > 0xF4) return false; - return true; -} - -/* --------------------------------------------------------------------- */ - -/* - * Exported function to return whether a UTF-8 sequence is legal or not. - * This is not used here; it's just exported. - */ -Booleano isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - ch = *source++; - if (flags == strictConversion ) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* - * Figure out how many bytes the result will require. Turn any - * illegally large UTF32 things (> Plane 17) into replacement chars. - */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - result = sourceIllegal; - } - - target += bytesToWrite; - if (target > targetEnd) { - --source; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF32* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up the source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_LEGAL_UTF32) { - /* - * UTF-16 surrogate values are illegal in UTF-32, and anything - * over Plane 17 (> 0x10FFFF) is illegal. - */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = ch; - } - } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ - result = sourceIllegal; - *target++ = UNI_REPLACEMENT_CHAR; - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- - - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: - { - int tmpBytesToRead = extraBytesToRead+1; - do { - ch += *source++; - --tmpBytesToRead; - if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); - } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. - - --------------------------------------------------------------------- */ - -////////////// - -#ifdef __cplusplus - -transcode_to_utf16::transcode_to_utf16(const char *utf8_input) -: _orig_length(int(strlen(utf8_input)) + 1), - _converted(new UTF16[_orig_length + 1]) - // we don't ever expect the string to get longer going to the larger data - // type, so the current length should be enough. -{ - _result = conversionOK; - if (_orig_length == 1) { - // no length, so only provide a blank string. - _converted[0] = 0; - return; - } - memset((abyte *)_converted, 0, 2 * _orig_length); - // we use these temporary pointers since the converter resets the source - // and target pointers to the end of the conversion. the same pattern - // is used in the code below. - const UTF8 *temp_in = (const UTF8 *)utf8_input; - UTF16 *temp_out = _converted; - _result = ConvertUTF8toUTF16(&temp_in, temp_in + _orig_length, - &temp_out, temp_out + _orig_length, lenientConversion); -} - -transcode_to_utf16::transcode_to_utf16(const astring &utf8_input) -: _orig_length(utf8_input.length() + 1), - _converted(new UTF16[_orig_length]) -{ - _result = conversionOK; - if (_orig_length == 1) { - // no length, so only provide a blank string. - _converted[0] = 0; - return; - } - memset((abyte *)_converted, 0, 2 * _orig_length); - const UTF8 *temp_in = (const UTF8 *)utf8_input.observe(); - UTF16 *temp_out = _converted; - _result = ConvertUTF8toUTF16(&temp_in, temp_in + _orig_length, - &temp_out, temp_out + _orig_length, lenientConversion); -} - -transcode_to_utf16::~transcode_to_utf16() -{ - delete [] _converted; - _converted = NIL; -} - -int transcode_to_utf16::length() const -{ return int(wcslen((wchar_t *)_converted)); } - -////////////// - -transcode_to_utf8::transcode_to_utf8(const UTF16 *utf16_input) -: _orig_length(int(wcslen((const wchar_t *)utf16_input))), - _new_length(_orig_length * 2 + _orig_length / 2 + 1), - // this is just an estimate. it may be appropriate most of the time. - // whatever doesn't fit will get truncated. - _converted(new UTF8[_new_length]) -{ - _result = conversionOK; - if (_orig_length == 0) { - // no length, so only provide a blank string. - _converted[0] = 0; - return; - } - memset(_converted, 0, _new_length); - const UTF16 *temp_in = (const UTF16 *)utf16_input; - UTF8 *temp_out = _converted; - _result = ConvertUTF16toUTF8(&temp_in, temp_in + _orig_length, - &temp_out, temp_out + _new_length, lenientConversion); -} - -transcode_to_utf8::transcode_to_utf8(const wchar_t *utf16_input) -: _orig_length(int(wcslen(utf16_input))), - _new_length(_orig_length * 2 + _orig_length / 2 + 1), - // this is just an estimate. it may be appropriate most of the time. - // whatever doesn't fit will get truncated. - _converted(new UTF8[_new_length > 0 ? _new_length : 1]) -{ - _result = conversionOK; - if (_orig_length == 0) { - // no length, so only provide a blank string. - _converted[0] = 0; - return; - } - memset(_converted, 0, _new_length); - const UTF16 *temp_in = (const UTF16 *)utf16_input; - UTF8 *temp_out = _converted; - _result = ConvertUTF16toUTF8(&temp_in, temp_in + _orig_length, - &temp_out, temp_out + _new_length, lenientConversion); -} - -transcode_to_utf8::~transcode_to_utf8() -{ - delete [] _converted; - _converted = NIL; -} - -int transcode_to_utf8::length() const -{ return int(strlen((char *)_converted)); } - -transcode_to_utf8::operator astring() const -{ return astring((char *)_converted); } - -////////////// - -null_transcoder::null_transcoder(const char *utf8_input, bool make_own_copy) -: _make_own_copy(make_own_copy), - _converted(make_own_copy? new UTF8[strlen(utf8_input) + 1] - : (const UTF8 *)utf8_input) -{ - if (_make_own_copy) { - strcpy((char *)_converted, utf8_input); - } -} - -null_transcoder::null_transcoder(const astring &utf8_input, bool make_own_copy) -: _make_own_copy(make_own_copy), - _converted(make_own_copy? new UTF8[utf8_input.length() + 1] - : (const UTF8 *)utf8_input.s()) -{ - if (_make_own_copy) { - strcpy((char *)_converted, utf8_input.s()); - } -} - -int null_transcoder::length() const -{ return int(strlen((char *)_converted)); } - -#endif //_cplusplus - -} //namespace. - diff --git a/core/library/basis/utf_conversion.h b/core/library/basis/utf_conversion.h deleted file mode 100644 index 33d436eb..00000000 --- a/core/library/basis/utf_conversion.h +++ /dev/null @@ -1,328 +0,0 @@ -#ifndef UTF_CONVERSION_GROUP -#define UTF_CONVERSION_GROUP - -/*****************************************************************************\ -* * -* Name : utf_conversion * -* Author : Unicode, Inc. (C conversion functions) * -* Author : Chris Koeritz (C++ conversion classes) * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "astring.h" -#include "definitions.h" - -//! @file utf_conversion.h Support for unicode builds. -/*! @file utf_conversion.h - This file provides conversions between UTF-8 and UTF-16 that are essential - to having the code compile in either the UNICODE or regular ANSI character - builds of win32-based code. -*/ - -// original copyright notice still applies to low-level conversion code: -/* - * Copyright 2001-$now Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - */ - -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several funtions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: , , - or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -namespace basis { - -/* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. ------------------------------------------------------------------------- */ -typedef unsigned long UTF32; /* at least 32 bits */ -typedef unsigned short UTF16; /* at least 16 bits */ -typedef unsigned char UTF8; /* typically 8 bits */ -typedef unsigned char Booleano; /* 0 or 1 */ - -//hmmm: this is some really gross stuff below, just because it's got -// so much of its guts hanging out. make a class out of the lower- -// level conversion stuff and hide all the details. - -/* Some fundamental constants */ -#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD -#define UNI_MAX_BMP (UTF32)0x0000FFFF -#define UNI_MAX_UTF16 (UTF32)0x0010FFFF -#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF -#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF - -typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insuff. room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ -} ConversionResult; - -typedef enum { - strictConversion = 0, - lenientConversion -} ConversionFlags; - -#ifdef __cplusplus -extern "C" { -#endif - -ConversionResult ConvertUTF8toUTF16 (const UTF8** sourceStart, - const UTF8* sourceEnd, UTF16** targetStart, UTF16* targetEnd, - ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF8 (const UTF16** sourceStart, - const UTF16* sourceEnd, UTF8** targetStart, UTF8* targetEnd, - ConversionFlags flags); - -ConversionResult ConvertUTF8toUTF32 (const UTF8** sourceStart, - const UTF8* sourceEnd, UTF32** targetStart, UTF32* targetEnd, - ConversionFlags flags); - -ConversionResult ConvertUTF32toUTF8 (const UTF32** sourceStart, - const UTF32* sourceEnd, UTF8** targetStart, UTF8* targetEnd, - ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF32 (const UTF16** sourceStart, - const UTF16* sourceEnd, UTF32** targetStart, UTF32* targetEnd, - ConversionFlags flags); - -ConversionResult ConvertUTF32toUTF16 (const UTF32** sourceStart, - const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, - ConversionFlags flags); - -Booleano isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); - -#ifdef __cplusplus -} //extern -#endif //cplusplus - -////////////// - -#ifdef __cplusplus - -// The following types and macros help to make it irrelevant what kind of -// win32 build is being done. They will adapt as needed to provide the -// types used in system calls. They are rendered harmless for other operating -// systems or for non-Unicode builds; this is especially useful for POSIX -// compliant functions that required Unicode in win32 but not in Unix systems. - -#if defined(UNICODE) -///holding to test wx && defined(__WIN32__) - //! to_unicode_temp() converts to UTF-16 as needed for win32 system calls. - /*! this conversion is only appropriate to use within expressions. it does - not create a permanent object and will go out of scope when the full - expression does. */ - #define to_unicode_temp(s) transcode_to_utf16(s) - //! the from_unicode_temp() macro converts the win32 unicode string to UTF-8. - /*! this has the same contraint as to_unicode_temp; it does not persist - beyond the lifetime of the expression containing it. */ - #define from_unicode_temp(s) transcode_to_utf8(s) - //! to_unicode_persist creates a UTF-16 object with block scope. - /*! the object with "name" that is created by this macro will exist on - the stack until the block containing it is closed. this is needed when - storing unicode strings into windows structure, for example. */ - #define to_unicode_persist(name, s) transcode_to_utf16 name(s) - //! from_unicode_persist creates a UTF-8 object with block scope. - /*! similar to to_unicode_persist, this is used for longer-lived objects. */ - #define from_unicode_persist(name, s) transcode_to_utf8 name(s) -#else - // these versions of the macros simply defang any conversions. - #define to_unicode_temp(s) null_transcoder(s, false) - #define from_unicode_temp(s) null_transcoder(s, false) - #define to_unicode_persist(name, s) null_transcoder name(s, true) - #define from_unicode_persist(name, s) null_transcoder name(s, true) -#endif - -#ifdef _MSC_VER - //! sends UTF-8 information to the diagnostic view in the IDE. - #define TRACE_PRINT(s) TRACE(_T("%s"), to_unicode_temp(s)) -#endif - -////////////// - -// The next two classes support converting a UTF-8 string into a UTF-16 -// string and vice-versa. They hold onto the converted string and provide -// operators that return it. - -//! handles the conversion of UTF-8 format strings into UTF-16. - -class transcode_to_utf16 : public virtual root_object -{ -public: - transcode_to_utf16(const char *utf8_input); - //!< translates the "utf8_input" from UTF-8 into UTF-16 format. - /*!< the resulting memory is held in this object until destruction, which - makes this especially convenient for in-place conversions. */ - - transcode_to_utf16(const astring &utf8_input); - //!< this conversion converts the astring in "utf8_input" into UTF-16. - - virtual ~transcode_to_utf16(); - - int length() const; - //!< reports the length of the double-byte-based converted string. - - operator const UTF16 * () const { return _converted; } - //!< observes the converted version of the string (constant version). - operator UTF16 * () { return _converted; } - //!< accesses the converted version of the string (modifiable version). - operator const flexichar * () const { return (const flexichar *)_converted; } - //!< accesses the converted version of the string (modifiable version). - operator flexichar * () { return (flexichar *)_converted; } - //!< accesses the converted version of the string (modifiable version). - - ConversionResult _result; -private: - int _orig_length; //!< how long is the original argument? - UTF16 *_converted; //!< the double-byte version of the original string. -}; - -////////////// - -//! handles the conversion of UTF-16 format strings into UTF-8. - -class transcode_to_utf8 : public virtual root_object -{ -public: - transcode_to_utf8(const UTF16 *utf16_input); - //!< translates the "utf16_input" from UTF-16 into UTF-8 format. - /*!< the resulting memory is held in this object until destruction, which - makes this especially convenient for in-place conversions. */ - - transcode_to_utf8(const wchar_t *utf16_input); - //!< converts win32 style wide characters to UTF-8. - - virtual ~transcode_to_utf8(); - - int length() const; - //!< reports the length of the byte-based converted string. - - operator const UTF8 * () const { return _converted; } - //!< observes the converted version of the string (constant version). - operator UTF8 * () { return _converted; } - //!< accesses the converted version of the string (modifiable version). - - operator astring() const; - //!< converts the char pointer into an astring object. - - ConversionResult _result; -private: - int _orig_length; //!< how long is the original argument? - int _new_length; //!< what did we predictively allocate for the result? - UTF8 *_converted; //!< the double-byte version of the original string. -}; - -////////////// - -//! provides an interface like the transcoders above, but stays in UTF-8. - -class null_transcoder : public virtual root_object -{ -public: - //! allocates memory for the converted form if "make_own_copy" is true. - null_transcoder(const char *utf8_input, bool make_own_copy); - //! similar, but supports astrings. - null_transcoder(const astring &utf8_input, bool make_own_copy); - ~null_transcoder() { - if (_make_own_copy) delete [] _converted; - _converted = NIL; - } - - int length() const; - operator char * () { return (char *)_converted; } - operator const char * () const { return (const char *)_converted; } - -private: - bool _make_own_copy; - const UTF8 *_converted; -}; - -#endif //cplusplus - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/configuration/application_configuration.cpp b/core/library/configuration/application_configuration.cpp deleted file mode 100644 index a8f463ea..00000000 --- a/core/library/configuration/application_configuration.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* -* Name : application_configuration -* Author : Chris Koeritz - -* Copyright (c) 1994-$now By Author. 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 2 of -* the License or (at your option) any later version. This is online at: -* http://www.fsf.org/copyleft/gpl.html -* Please send any updates to: fred@gruntose.com -*/ - -#include "application_configuration.h" -#include "ini_configurator.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __APPLE__ - #include - #include -#endif -#ifdef __WIN32__ - #include - #include -#else - #include -#endif -#ifdef __UNIX__ - #include - #include -#endif -#include -#include -#include -#include - -using namespace basis; -using namespace filesystem; -using namespace mathematics; -using namespace structures; -using namespace textual; - -#undef LOG -#define LOG(to_print) printf("%s\n", astring(to_print).s()) - -namespace configuration { - -const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; - // maximum command line that we'll deal with here. - -#ifdef __UNIX__ -astring application_configuration::get_cmdline_from_proc() -{ - FUNCDEF("get_cmdline_from_proc"); - static astring __check_once_app_path; -//hmmm: we want to use a single per app static synch here! - if (__check_once_app_path.length()) return __check_once_app_path; - -#ifdef __APPLE__ - __check_once_app_path = query_for_process_info(); - return __check_once_app_path; -#endif - - // we have not looked this app's name up in the path yet. - a_sprintf cmds_filename("/proc/%d/cmdline", process_id()); - FILE *cmds_file = fopen(cmds_filename.s(), "r"); - if (!cmds_file) { - LOG("failed to open our process's command line file.\n"); - return "unknown"; - } -//hmmm: this would be a lot nicer using a byte filer. - size_t size = 2000; - char *filebuff = new char[size + 1]; - ssize_t chars_read = getline((char **)&filebuff, &size, cmds_file); - // read the first line, giving ample space for how long it might be. - fclose(cmds_file); // drop the file again. - if (!chars_read || negative(chars_read)) { - LOG("failed to get any characters from our process's cmdline file.\n"); - return "unknown"; - } - // copy the buffer into a string, which works great since the entries in the - // command line are all separated by zero characters. - __check_once_app_path = filebuff; - delete [] filebuff; - // clean out quote characters from the name. - for (int i = __check_once_app_path.length() - 1; i >= 0; i--) { - if (__check_once_app_path[i] == '"') __check_once_app_path.zap(i, i); - } - // check if the thing has a path attached to it. if it doesn't, we need to accentuate - // our knowledge about the file. - filename testing(__check_once_app_path); - if (testing.had_directory()) return __check_once_app_path; // all set. - -//hmmm: the below might be better off as a find app in path method, which relies on which. - - // there was no directory component, so we'll try to guess one. - astring temp_filename(environment::get("TMP") - + a_sprintf("/zz_cmdfind.%d", chaos().inclusive(0, 999999999))); - system((astring("which ") + __check_once_app_path + " >" + temp_filename).s()); - FILE *which_file = fopen(temp_filename.s(), "r"); - if (!which_file) { - LOG("failed to open the temporary output from which.\n"); - return "unknown"; - } - // reallocate the file buffer. - size = 2000; - filebuff = new char[size + 1]; - chars_read = getline((char **)&filebuff, &size, which_file); - fclose(which_file); - unlink(temp_filename.s()); - if (!chars_read || negative(chars_read)) { - LOG("failed to get any characters from the which cmd output.\n"); - return "unknown"; - } else { - // we had some luck using 'which' to locate the file, so we'll use this version. - __check_once_app_path = filebuff; - while (parser_bits::is_eol(__check_once_app_path[__check_once_app_path.end()])) { - __check_once_app_path.zap(__check_once_app_path.end(), __check_once_app_path.end()); - } - } - delete [] filebuff; - return __check_once_app_path; // return whatever which told us. -} - -// deprecated; better to use the /proc/pid/cmdline file. -astring application_configuration::query_for_process_info() -{ -// FUNCDEF("query_for_process_info"); - astring to_return = "unknown"; - // we ask the operating system about our process identifier and store - // the results in a temporary file. - chaos rando; - a_sprintf tmpfile("/tmp/proc_name_check_%d_%d.txt", process_id(), - rando.inclusive(0, 128000)); -#ifdef __APPLE__ - a_sprintf cmd("ps -o args=\"\" %d >%s", process_id(), - tmpfile.s()); -#else - a_sprintf cmd("ps h -O \"args\" %d >%s", process_id(), - tmpfile.s()); -#endif - // run the command to locate our process info. - int sysret = system(cmd.s()); - if (negative(sysret)) { - LOG("failed to run ps command to get process info"); - return to_return; - } - // open the output file for reading. - FILE *output = fopen(tmpfile.s(), "r"); - if (!output) { - LOG("failed to open the ps output file"); - return to_return; - } - // read the file's contents into a string buffer. - char buff[MAXIMUM_COMMAND_LINE]; - size_t size_read = fread(buff, 1, MAXIMUM_COMMAND_LINE, output); - if (size_read > 0) { - // success at finding some text in the file at least. - while (size_read > 0) { - const char to_check = buff[size_read - 1]; - if ( !to_check || (to_check == '\r') || (to_check == '\n') - || (to_check == '\t') ) - size_read--; - else break; - } - to_return.reset(astring::UNTERMINATED, buff, size_read); - } else { - // couldn't read anything. - LOG("could not read output of process list"); - } - unlink(tmpfile.s()); - return to_return; -} -#endif - -// used as a return value when the name cannot be determined. -#define SET_BOGUS_NAME(error) { \ - LOG(error); \ - if (output) { \ - fclose(output); \ - unlink(tmpfile.s()); \ - } \ - astring home_dir = env_string("HOME"); \ - to_return = home_dir + "/failed_to_determine.exe"; \ -} - -astring application_configuration::application_name() -{ -// FUNCDEF("application_name"); - astring to_return; -#ifdef __APPLE__ - char buffer[MAX_ABS_PATH] = { '\0' }; - uint32_t buffsize = MAX_ABS_PATH - 1; - _NSGetExecutablePath(buffer, &buffsize); - to_return = (char *)buffer; -#elif __UNIX__ - to_return = get_cmdline_from_proc(); -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetModuleFileName(NIL, low_buff, MAX_ABS_PATH - 1); - astring buff = from_unicode_temp(low_buff); - buff.to_lower(); // we lower-case the name since windows seems to UC it. - to_return = buff; -#else - #pragma error("hmmm: no means of finding app name is implemented.") - SET_BOGUS_NAME("not_implemented_for_this_OS"); -#endif - return to_return; -} - -#if defined(__UNIX__) || defined(__WIN32__) - basis::un_int application_configuration::process_id() { return getpid(); } -#else - #pragma error("hmmm: need process id implementation for this OS!") - basis::un_int application_configuration::process_id() { return 0; } -#endif - -astring application_configuration::current_directory() -{ - astring to_return; -#ifdef __UNIX__ - char buff[MAX_ABS_PATH]; - getcwd(buff, MAX_ABS_PATH - 1); - to_return = buff; -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetCurrentDirectory(MAX_ABS_PATH, low_buff); - to_return = from_unicode_temp(low_buff); -#else - #pragma error("hmmm: need support for current directory on this OS.") - to_return = "."; -#endif - return to_return; -} - -// implement the software product function. -const char *application_configuration::software_product_name() -{ -#ifdef GLOBAL_PRODUCT_NAME - return GLOBAL_PRODUCT_NAME; -#else - return "hoople"; -#endif -} - -astring application_configuration::application_directory() -{ return filename(application_name()).dirname().raw(); } - -structures::version application_configuration::get_OS_version() -{ - version to_return; -#ifdef __UNIX__ - utsname kernel_parms; - uname(&kernel_parms); - to_return = version(kernel_parms.release); -#elif defined(__WIN32__) - OSVERSIONINFO info; - info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - ::GetVersionEx(&info); - to_return = version(a_sprintf("%u.%u.%u.%u", basis::un_short(info.dwMajorVersion), - basis::un_short(info.dwMinorVersion), basis::un_short(info.dwPlatformId), - basis::un_short(info.dwBuildNumber))); -#else - #pragma error("hmmm: need version info for this OS!") -#endif - return to_return; -} - -////////////// - -const char *PATH_CONFIGURATION_FILENAME() { return "paths.ini"; } - -astring application_configuration::application_configuration_file() -{ - filename cfg_file(application_directory() + "/" + PATH_CONFIGURATION_FILENAME()); - return cfg_file.raw(); -} - -const astring &application_configuration::GLOBAL_SECTION_NAME() { STATIC_STRING("Common"); } - -const astring &application_configuration::LOGGING_FOLDER_NAME() { STATIC_STRING("LogPath"); } - -////////////// - -const int MAX_LOG_PATH = 200; - // the maximum length of the entry stored for the log path. - -/* -astring application_configuration::installation_root() -{ - astring to_return = read_item(LOCAL_FOLDER_NAME()); - if (!to_return) { - // well, if this other guy has a path, we'll give that back. otherwise, - // we don't know what the path should be at all. - to_return = filename(application_configuration_file()).dirname(); - } - return to_return; -} -*/ - -astring application_configuration::get_logging_directory() -{ - // start with the root of our installation. - astring def_log = application_directory(); -///installation_root(); - // add logs directory underneath that. - def_log += "/logs"; - // add the subdirectory for logs. - - // now grab the current value for the name, if any. - astring log_dir = read_item(LOGGING_FOLDER_NAME()); - // get the entry for the logging path. - if (!log_dir) { - // if the entry was absent, we set it. - ini_configurator ini(application_configuration_file(), - ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY); - ini.store(GLOBAL_SECTION_NAME(), LOGGING_FOLDER_NAME(), def_log); - } else { - // they gave us something. let's replace the environment variables - // in their string so we resolve paths and such. - log_dir = parser_bits::substitute_env_vars(log_dir); - } - - // now we make sure the directory exists. - struct stat to_fill; - int stat_ret = stat(log_dir.observe(), &to_fill); - if (stat_ret || !(to_fill.st_mode & S_IFDIR) ) { - // if it's not anything yet or if it's not a directory, then we need - // to create it. -//if it's something besides a directory... should it be deleted? -#ifdef __UNIX__ - int mk_ret = mkdir(log_dir.s(), 0777); -#endif -#ifdef __WIN32__ - int mk_ret = mkdir(log_dir.s()); -#endif - if (mk_ret) return ""; -//can't have a log file if we can't make the directory successfully??? - } - - return log_dir; -} - -astring application_configuration::make_logfile_name(const astring &base_name) -{ return get_logging_directory() + "/" + base_name; } - -///astring application_configuration::core_bin_directory() { return read_item("core_bin"); } - -astring application_configuration::read_item(const astring &key_name) -{ - filename ini_name = application_configuration_file(); - ini_configurator ini(ini_name, ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY); - astring to_return = ini.load(GLOBAL_SECTION_NAME(), key_name, ""); - if (!!to_return) { - // if the string has any length, then we process any environment - // variables found encoded in the value. - to_return = parser_bits::substitute_env_vars(to_return); - } - return to_return; -} - -} // namespace. - diff --git a/core/library/configuration/application_configuration.h b/core/library/configuration/application_configuration.h deleted file mode 100644 index e51bb210..00000000 --- a/core/library/configuration/application_configuration.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef APPLICATION_CONFIGURATION_GROUP -#define APPLICATION_CONFIGURATION_GROUP - -/*****************************************************************************\ -* * -* Name : path configuration definitions * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace configuration { - -//! Defines installation-specific locations in the file system. - -class application_configuration : public virtual basis::root_object -{ -public: - virtual ~application_configuration() {} - - // these methods are mainly about the application itself. - - static basis::astring application_name(); - //!< returns the full name of the current application. - - static basis::astring application_directory(); - //!< returns the directory name where this application is running from. - - static basis::un_int process_id(); - //!< returns the process id for this task, if that's relevant on the OS. - - // these interface with the operating system. - - static structures::version get_OS_version(); - //!< returns the operating system's version information. - /*!< for linux, this has: major/minor/release/revision as components. - for ms-windows, this has: major/minor/platform_ID/build_number. */ - - static basis::astring current_directory(); - //!< returns the current directory as reported by the operating system. - - // the following are more about the installation than this application... - - static const char *software_product_name(); - //!< This global function is available to the system at large for branding info. - -// static basis::astring installation_root(); - //!< returns the top-level directory of this installation. - /*!< returns the folder containing the application directory (usually) as - well as the other folders of configuration information and fonts and - such needed by this installation. */ - - static const char *APPLICATION_CONFIGURATION_FILENAME(); - //!< the configuration file provides a set of paths needed here. - /*!< this file stores paths that the low-level libraries need for - proper operation. this is just the base filename; the fully qualified - path to the path configuration file is provided below. */ - - static basis::astring application_configuration_file(); - //!< the fully specified path to the main path configuration file. - /*!< the location of this file is determined by the directory where this - executable is running. this is the only configuration file that should - reside there, given the itchy vista compliance needs. */ - -//// static basis::astring core_bin_directory(); - //!< retrieves the core binary directory location from paths.ini. - - static basis::astring get_logging_directory(); - //!< returns the directory where log files will be stored. - - // the following are key names within the main configuration file. - - static const basis::astring &GLOBAL_SECTION_NAME(); - //!< the root section name for our configuration items in the main ini file. - /*!< within the configuration file, there is a section named as above. - this section should only be used to define path configuration entries that - affect the general operation of the system. entries that are specific - to particular programs or subsystems should be contained in their own - section. */ - -/// static const basis::astring &LOCAL_FOLDER_NAME(); - //!< entry name in the config file that points at the installation root. - /*!< this is where all files for this product are stored on "this" machine. */ - - static const basis::astring &LOGGING_FOLDER_NAME(); - //!< the location where the log files for the system are stored. - /*!< this is always considered to be a directory under the local folder. - the make_logfile_name() function (see below) can be used to create a - properly formed filename for logging. */ - - // helper methods. - - static basis::astring read_item(const basis::astring &key_name); - //!< returns the entry listed under the "key_name". - /*!< if the "key_name" is valid for the root configuration file, then this - should always return an appropriate value. a failure is denoted by return - of an empty string. */ - - static basis::astring make_logfile_name(const basis::astring &base_name); - //!< generates an installation appropriate log file name from "base_name". - /*!< creates and returns a full path name for a log file with the - "base_name" specified, using the LOGGING_FOLDER_NAME() entry as the - directory name. if the logging directory does not exist, it is created if - that is possible. the returned name is suitable for logging mechanisms that - need a filename. an empty string is returned on failure to create the - directory. */ - -#ifdef __UNIX__ - #ifdef __APPLE__ - static basis::astring get_cmdline_for_apple(); - #endif - static basis::astring get_cmdline_from_proc(); - //!< retrieves the command line from the /proc hierarchy on linux. - static basis::astring query_for_process_info(); - //!< seeks out process info for a particular process. -#endif -}; - -} // namespace. - -#endif - diff --git a/core/library/configuration/config_watcher.cpp b/core/library/configuration/config_watcher.cpp deleted file mode 100644 index e54b475f..00000000 --- a/core/library/configuration/config_watcher.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/*****************************************************************************\ -* * -* Name : config_watcher * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2008-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "config_watcher.h" - -#include -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace configuration { - -config_watcher::config_watcher(configurator &to_watch) -: _watching(to_watch), - _current_config(new table_configurator), - _previous_config(new table_configurator) -{ - rescan(); // fill out our lists. -} - -config_watcher::~config_watcher() -{ - WHACK(_current_config); - WHACK(_previous_config); -} - -bool config_watcher::rescan() -{ - // copy the current configuration into our previous config tracker. - *_previous_config = *_current_config; - // clean out any current items held. - _current_config->reset(); - - // iterate across the sections in the watched config. - string_set sects; - _watching.section_set(sects); - for (int sectindy = 0; sectindy < sects.length(); sectindy++) { - // every entry in the current section gets added to our current config. - astring curr_section = sects[sectindy]; - string_table entries; - _watching.get_section(curr_section, entries); - _current_config->put_section(curr_section, entries); - } - - return true; -} - -string_set config_watcher::new_sections() const -{ - string_set before; - _previous_config->section_set(before); - string_set after; - _current_config->section_set(before); - return after - before; -} - -string_set config_watcher::deleted_sections() const -{ - string_set before; - _previous_config->section_set(before); - string_set after; - _current_config->section_set(before); - return before - after; -} - -string_set config_watcher::changed_sections() const -{ - string_set before; - _previous_config->section_set(before); - string_set after; - _current_config->section_set(before); - string_set possible_changes = before.intersection(after); - string_set definite_changes; - for (int i = 0; i < possible_changes.elements(); i++) { - const astring §_name = possible_changes[i]; - string_table previous_section; - _previous_config->get_section(sect_name, previous_section); - string_table current_section; - _current_config->get_section(sect_name, current_section); - if (current_section != previous_section) - definite_changes += sect_name; - } - return definite_changes; -} - -string_set config_watcher::deleted_items(const astring §ion_name) -{ - string_table previous_section; - _previous_config->get_section(section_name, previous_section); - string_set previous_names; - previous_section.names(previous_names); - string_table current_section; - _current_config->get_section(section_name, current_section); - string_set current_names; - current_section.names(current_names); - return previous_names - current_names; -} - -string_set config_watcher::new_items(const astring §ion_name) -{ - string_table previous_section; - _previous_config->get_section(section_name, previous_section); - string_set previous_names; - previous_section.names(previous_names); - string_table current_section; - _current_config->get_section(section_name, current_section); - string_set current_names; - current_section.names(current_names); - return current_names - previous_names; -} - -string_set config_watcher::changed_items(const astring §ion_name) -{ - string_table previous_section; - _previous_config->get_section(section_name, previous_section); - string_set previous_names; - previous_section.names(previous_names); - string_table current_section; - _current_config->get_section(section_name, current_section); - string_set current_names; - current_section.names(current_names); - - string_set possible_changes = current_names.intersection(previous_names); - string_set definite_changes; - for (int i = 0; i < possible_changes.elements(); i++) { - const astring &curr_item = possible_changes[i]; - astring prev_value; - _previous_config->get(section_name, curr_item, prev_value); - astring curr_value; - _current_config->get(section_name, curr_item, curr_value); - if (prev_value != curr_value) - definite_changes += curr_item; - } - return definite_changes; -} - -} //namespace. - - diff --git a/core/library/configuration/config_watcher.h b/core/library/configuration/config_watcher.h deleted file mode 100644 index bc32345c..00000000 --- a/core/library/configuration/config_watcher.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CONFIG_WATCHER_CLASS -#define CONFIG_WATCHER_CLASS - -/*****************************************************************************\ -* * -* Name : config_watcher * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2008-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" -#include "table_configurator.h" - -#include -#include - -namespace configuration { - -//! an object that watches the contents of a configurator for changes. -/*! - when given a configurator object to check, this will initially build an - exact copy of the contents seen. when asked to look for changes to the - configurator, the previous version is compared with the current state and - any changed sections are reported. -*/ - -class config_watcher -{ -public: - config_watcher(configurator &to_watch); - //!< watches the configurator for changes and tracks them. - /*!< "to_watch" must exist for lifetime of this class. */ - - virtual ~config_watcher(); - - DEFINE_CLASS_NAME("config_watcher"); - - bool rescan(); - //!< updates the configurator snapshot and enables the comparison methods. - /*!< call this before testing the changed() method. */ - - // these lists describe how sections have changed, if at all. - structures::string_set new_sections() const; - structures::string_set deleted_sections() const; - structures::string_set changed_sections() const; - - // methods for comparing changes within sections in the config. - structures::string_set new_items(const basis::astring §ion_name); - structures::string_set deleted_items(const basis::astring §ion_name); - structures::string_set changed_items(const basis::astring §ion_name); - -private: - configurator &_watching; //!< the config object we examine. - table_configurator *_current_config; //!< most current records. - table_configurator *_previous_config; //!< records we saw earlier. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/configlet.cpp b/core/library/configuration/configlet.cpp deleted file mode 100644 index 97e324e4..00000000 --- a/core/library/configuration/configlet.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/*****************************************************************************\ -* * -* Name : configlet * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configlet.h" -#include "configurator.h" - -#include -#include - -using namespace basis; - -namespace configuration { - -const astring bogus_default = "OOPS: not supposed to ever be seen. d'oh!"; - -////////////// - -configlet::configlet(const astring §ion, const astring &entry) -: _section(new astring(section)), - _entry(new astring(entry)) -{} - -configlet::configlet(const configlet &to_copy) -: _section(new astring(*to_copy._section)), - _entry(new astring(*to_copy._entry)) -{} - -configlet::~configlet() -{ - WHACK(_section); - WHACK(_entry); -} - -configlet &configlet::operator =(const configlet &to_copy) -{ - if (this == &to_copy) return *this; - *_section = *to_copy._section; - *_entry = *to_copy._entry; - return *this; -} - -const astring &configlet::section() const { return *_section; } - -const astring &configlet::entry() const { return *_entry; } - -void configlet::section(const astring &new_section) const -{ *_section = new_section; } - -void configlet::entry(const astring &new_entry) const -{ *_entry = new_entry; } - -////////////// - -string_configlet::string_configlet(const astring §ion, const astring &entry, - const astring ¤t_value, const astring &default_value) -: configlet(section, entry), - _current(new astring(current_value)), - _default(new astring(default_value)) -{} - -string_configlet::string_configlet(const string_configlet &to_copy) -: configlet(to_copy.section(), to_copy.entry()), - _current(new astring(*to_copy._current)), - _default(new astring(*to_copy._default)) -{ -} - -string_configlet::~string_configlet() -{ - WHACK(_current); - WHACK(_default); -} - -string_configlet &string_configlet::operator =(const string_configlet &to_copy) -{ - if (this == &to_copy) return *this; - (configlet &)*this = to_copy; - *_current = *to_copy._current; - *_default = *to_copy._default; - return *this; -} - -const astring &string_configlet::current_value() const { return *_current; } - -const astring &string_configlet::default_value() const { return *_default; } - -void string_configlet::current_value(const astring &new_current) -{ *_current = new_current; } - -void string_configlet::default_value(const astring &new_default) -{ *_default = new_default; } - -bool string_configlet::load(configurator &config) -{ - if (config.get(section(), entry(), *_current)) return true; // success. - // we failed to read the value... - *_current = *_default; - if (config.behavior() == configurator::AUTO_STORE) - config.put(section(), entry(), *_current); - return false; // don't hide that it wasn't there. -} - -bool string_configlet::store(configurator &config) const -{ return config.put(section(), entry(), *_current); } - -configlet *string_configlet::duplicate() const -{ return new string_configlet(section(), entry(), *_current, *_default); } - -////////////// - -int_configlet::int_configlet(const astring §ion, const astring &entry, - int current_value, int default_value) -: configlet(section, entry), - _current(current_value), - _default(default_value) -{ -} - -int_configlet::~int_configlet() {} - -void int_configlet::current_value(int new_current) -{ _current = new_current; } - -bool int_configlet::load(configurator &config) -{ - astring temp; - bool to_return = config.get(section(), entry(), temp); - // just check if it was already there. - int temp_c = config.load(section(), entry(), _default); - current_value(temp_c); - return to_return; -} - -bool int_configlet::store(configurator &config) const -{ return config.store(section(), entry(), _current); } - -configlet *int_configlet::duplicate() const -{ return new int_configlet(*this); } - -////////////// - -bounded_int_configlet::bounded_int_configlet(const astring §ion, - const astring &entry, int current_value, int default_value, - int minimum, int maximum) -: int_configlet(section, entry, current_value, default_value), - _minimum(minimum), - _maximum(maximum) -{ -} - -bounded_int_configlet::~bounded_int_configlet() {} - -void bounded_int_configlet::current_value(int new_current) -{ - if (new_current < _minimum) - new_current = default_value(); - if (new_current > _maximum) - new_current = default_value(); - int_configlet::current_value(new_current); -} - -configlet *bounded_int_configlet::duplicate() const -{ return new bounded_int_configlet(*this); } - -} //namespace. - diff --git a/core/library/configuration/configlet.h b/core/library/configuration/configlet.h deleted file mode 100644 index bd848c92..00000000 --- a/core/library/configuration/configlet.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef CONFIGLET_CLASS -#define CONFIGLET_CLASS - -/*****************************************************************************\ -* * -* Name : configlet * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" - -#include -#include - -namespace configuration { - -//! Represents an atom of configuration info. -/*! - The configlet has a location in a configuration repository that is defined - by its section and key name. Derived types can also have a value that is - stored in that location. -*/ - -class configlet : public virtual basis::root_object -{ -public: - configlet(const basis::astring §ion, const basis::astring &entry); - //!< creates a configlet that lives in the "section" at the "entry". - configlet(const configlet &to_copy); - - virtual ~configlet(); - - DEFINE_CLASS_NAME("configlet"); - - configlet &operator =(const configlet &to_copy); - - const basis::astring §ion() const; - //!< observes the section of this configlet. - const basis::astring &entry() const; - //!< observes the entry name of this configlet. - - void section(const basis::astring &new_section) const; - //!< modifies the configlet section location. - void entry(const basis::astring &new_entry) const; - //!< modifies the configlet entry name. - - virtual bool load(configurator &config) = 0; - //!< retrieves the configlet's information from the "config". - /*!< true is returned when this is successful. note that false is returned - if the entry was not originally present; if the configurator has the - AUTO_STORE behavior, then we will write out the default value on failure. - the next load() would be a success in that case, but would return the - default. */ - - virtual bool store(configurator &config) const = 0; - //!< writes the configlet's information out to the "config". - - virtual configlet *duplicate() const = 0; - //!< a virtual copy constructor for configlets. - /*!< the returned object will be a new copy of this configlet. */ - -private: - basis::astring *_section; //!< the section name, with whatever form is appropriate. - basis::astring *_entry; //!< the entry name in the native representation. -}; - -////////////// - -//! a string_configlet holds onto a character string value. -/*! - it has a current value, which could change due to updates to the - configuration, and a default value that probably won't change as often. - if the "load" operation fails, the default value will be used. -*/ - -class string_configlet : public configlet -{ -public: - string_configlet(const basis::astring §ion, const basis::astring &entry, - const basis::astring ¤t_value = basis::astring::empty_string(), - const basis::astring &default_value = basis::astring::empty_string()); - string_configlet(const string_configlet &to_copy); - virtual ~string_configlet(); - - string_configlet &operator =(const string_configlet &to_copy); - - const basis::astring ¤t_value() const; - const basis::astring &default_value() const; - - void current_value(const basis::astring &new_current); - void default_value(const basis::astring &new_default); - - virtual bool load(configurator &config); - virtual bool store(configurator &config) const; - - configlet *duplicate() const; - -private: - basis::astring *_current; - basis::astring *_default; -}; - -////////////// - -//! Stores a simple integer in a configuration repository. - -class int_configlet : public configlet -{ -public: - int_configlet(const basis::astring §ion, const basis::astring &entry, - int current_value = 0, int default_value = 0); - virtual ~int_configlet(); - - int current_value() const { return _current; } - - virtual void current_value(int new_current); - //!< the modifier function is virtual so derived classes can extend. - - int default_value() const { return _default; } - void default_value(int new_default) { _default = new_default; } - - virtual bool load(configurator &config); - virtual bool store(configurator &config) const; - - configlet *duplicate() const; - -private: - int _current; - int _default; -}; - -////////////// - -//! Stores an integer in a configuration repository with range checking. -/*! - a bounded_int_configlet has current and default values but also specifies a - valid range for the current value. if the current value falls outside - of that range (even via a "set" operation), then the default value is - used for the current. -*/ - -class bounded_int_configlet : public int_configlet -{ -public: - bounded_int_configlet(const basis::astring §ion, const basis::astring &entry, - int current_value, int default_value, int minimum, int maximum); - virtual ~bounded_int_configlet(); - - virtual void current_value(int new_current); - - int minimum() const { return _minimum; } - int maximum() const { return _maximum; } - - void minimum(int new_min) { _minimum = new_min; } - void maximum(int new_max) { _maximum = new_max; } - - configlet *duplicate() const; - -private: - int _minimum; - int _maximum; -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/configurable.h b/core/library/configuration/configurable.h deleted file mode 100644 index c40f54f7..00000000 --- a/core/library/configuration/configurable.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef CONFIGURABLE_CLASS -#define CONFIGURABLE_CLASS - -/*****************************************************************************\ -* * -* Name : configurable * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! base class for objects that support configuration_list updates. -/*! - The configuration_list implements a set of configuration items and it can - be used to represent a "delta" between the current configuration of an - object and a new configuration that is desired. Objects based on the - configurable class support taking that delta chunk of configuration info - and adapting their current internal configuration to meet the request, if - possible. These objects also support querying their current configuration, - which reports all configuration item names and current settings. -*/ - -#include - - -// forward. -class configuration_list; - -class configurable : public virtual root_object -{ -public: - virtual ~configurable() {} - - DEFINE_CLASS_NAME("configurable"); - - virtual void get_config(configuration_list &to_fill, bool append) const = 0; - //!< interprets the contents of this object as a configuration list. - /*!< the list of configlets can be stored in any configurator object. - if the "append" flag is true, then the list is added to. otherwise it - is cleared first. this method can also be used to retrieve the configlets - that this class defines as its configuration interface. that list can - then be filled in using a configurator and passed to set_config(). */ - - virtual bool set_config(const configuration_list &to_use) = 0; - //!< retrieves the config items from "to_use" and stores them here. - /*!< false is returned if any of the key items are missing or if the - new key cannot be decoded. */ -}; - -#endif - diff --git a/core/library/configuration/configuration_list.cpp b/core/library/configuration/configuration_list.cpp deleted file mode 100644 index 97b79650..00000000 --- a/core/library/configuration/configuration_list.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/*****************************************************************************\ -* * -* Name : configuration_list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configlet.h" -#include "configuration_list.h" - -#include -#include - -#include - -using namespace basis; -using namespace structures; - -namespace configuration { - -class cl_figlet_list : public amorph {}; - -////////////// - -configuration_list::configuration_list() -: _figs(new cl_figlet_list) -{ -} - -configuration_list::~configuration_list() -{ - WHACK(_figs); -} - -void configuration_list::reset() { _figs->reset(); } - -void configuration_list::add(const configlet &new_item) -{ - zap(new_item); - _figs->append(new_item.duplicate()); -} - -const configlet *configuration_list::find(const configlet &to_find) const -{ - for (int i = 0; i < _figs->elements(); i++) { - configlet &curr = *_figs->borrow(i); - if ( (to_find.section() == curr.section()) - && (to_find.entry() == curr.entry()) - && (typeid(curr) == typeid(to_find)) ) { - return &curr; - } - } - return NIL; -} - -bool configuration_list::zap(const configlet &dead_item) -{ - for (int i = 0; i < _figs->elements(); i++) { - configlet &curr = *_figs->borrow(i); - if ( (dead_item.section() == curr.section()) - && (dead_item.entry() == curr.entry()) ) { - _figs->zap(i, i); - return true; - } - } - return false; -} - -bool configuration_list::load(configurator &config) -{ - bool to_return = true; - for (int i = 0; i < _figs->elements(); i++) { - configlet &curr = *_figs->borrow(i); - if (!curr.load(config)) to_return = false; // any failure is bad. - } - return to_return; -} - -bool configuration_list::store(configurator &config) const -{ - bool to_return = true; - for (int i = 0; i < _figs->elements(); i++) { - configlet &curr = *_figs->borrow(i); - if (!curr.store(config)) to_return = false; // any failure is bad. - } - return to_return; -} - -} //namespace. - diff --git a/core/library/configuration/configuration_list.h b/core/library/configuration/configuration_list.h deleted file mode 100644 index 0aea0ca7..00000000 --- a/core/library/configuration/configuration_list.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef CONFIGURATION_LIST_CLASS -#define CONFIGURATION_LIST_CLASS - -/*****************************************************************************\ -* * -* Name : configuration_list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace configuration { - -// forward. -class cl_figlet_list; -class configlet; -#include - -//! Manages a collection of configlet objects. -/*! - This class provides the ability to operate on the collection of configlets - as a whole. They can be retrieved from or stored to a configurator object. -*/ - -class configuration_list : public virtual basis::root_object -{ -public: - configuration_list(); - virtual ~configuration_list(); - - DEFINE_CLASS_NAME("configuration_list"); - - void reset(); //!< removes all items from the list. - - void add(const configlet &new_item); - //!< adds another configuration atom into the list. - - const configlet *find(const configlet &to_find) const; - //!< locates the actual configlet with the section and entry of "to_find". - /*!< note that this might fail if no matching section and entry are found, - thus returning NIL. the returned object is still kept in the list, so - do not try to destroy it. also note that the object passed in must be - the same type as the object to be found; otherwise, NIL will be - returned. */ - - bool zap(const configlet &dead_item); - //!< removes a previously added configuration item. - /*!< the "dead_item" need only provide the section and entry names. */ - - //! reads the values of all the configlets stored in "config" into this. - bool load(configurator &config); - //! writes the current values of all the configlets in "this" into "config". - bool store(configurator &config) const; - -private: - cl_figlet_list *_figs; //!< our list of configlets. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/configurator.cpp b/core/library/configuration/configurator.cpp deleted file mode 100644 index 57cab64c..00000000 --- a/core/library/configuration/configurator.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/*****************************************************************************\ -* * -* Name : configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" - -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace configuration { - -configurator::~configurator() {} - -astring configurator::load(const astring §ion, const astring &entry, - const astring &default_string) -{ - astring to_return; - if (!get(section, entry, to_return)) { - to_return = default_string; - if (_behavior == AUTO_STORE) put(section, entry, to_return); - // save the entry back if we're in autostore mode. - } - return to_return; -} - -bool configurator::store(const astring §ion, const astring &entry, - const astring &to_store) -{ return put(section, entry, to_store); } - -bool configurator::store(const astring §ion, const astring &entry, - int value) -{ - return store(section, entry, astring(astring::SPRINTF, "%d", value)); -} - -void configurator::sections(string_array &list) -{ - // default implementation does nothing. - list = string_array(); -} - -void configurator::section_set(string_set &list) -{ - string_array temp; - sections(temp); - list = temp; -} - -int configurator::load(const astring §ion, const astring &entry, - int def_value) -{ - astring value_string; - if (!get(section, entry, value_string)) { - if (_behavior == AUTO_STORE) store(section, entry, def_value); - return def_value; - } - return value_string.convert(def_value); -} - -bool configurator::section_exists(const astring §ion) -{ - string_table infos; - // heavy-weight call here... - return get_section(section, infos); -} - -} //namespace. - diff --git a/core/library/configuration/configurator.h b/core/library/configuration/configurator.h deleted file mode 100644 index 6286a7c8..00000000 --- a/core/library/configuration/configurator.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef CONFIGURATOR_CLASS -#define CONFIGURATOR_CLASS - -/*****************************************************************************\ -* * -* Name : configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -namespace configuration { - -//! Provides a base class for configuration repositories. -/*! - All items that can be stored are modelled as having an entry name and a - value. Groups of entries are stored in sections, in which the data - usually have some relation to each other or share a common purpose. -*/ - -class configurator : public virtual basis::root_object -{ -public: - enum treatment_of_defaults { AUTO_STORE, RETURN_ONLY }; - //!< Governs how missing items are treated. - /*!< When the default value is used in the get() method below, it can - either be written to the ini file automatically (AUTO_STORE) or it can - just be returned (RETURN_ONLY). */ - - configurator(treatment_of_defaults behavior = RETURN_ONLY) : _behavior(behavior) {} - virtual ~configurator(); - - //! observes the behavior chosen for the load() function. - treatment_of_defaults behavior() const { return _behavior; } - //! modifies the behavior of the load() function. - void behavior(treatment_of_defaults new_behavior) { - _behavior = (new_behavior == RETURN_ONLY)? RETURN_ONLY : AUTO_STORE; - } - - virtual bool get(const basis::astring §ion, const basis::astring &entry, - basis::astring &found) = 0; - //!< Retrieves an item from the configuration store. - /*!< This retrieves a string into "found" that is listed in the "section" - specified under the "entry" name. if the string is not found, false is - returned. */ - - virtual bool put(const basis::astring §ion, const basis::astring &entry, - const basis::astring &to_store) = 0; - //!< Places an item into the configuration store. - /*!< places an entry into the "section" under the "entry" name using the - string "to_store". if the storage was successful, true is returned. - reasons for failure depend on the derived class implementations. */ - - bool store(const basis::astring §ion, const basis::astring &entry, - const basis::astring &to_store); - //!< a synonym for put. - - //! a synonym for get that implements the auto-store behavior. - /*! if the behavior is set to auto-store, then the default value will be - written when no value existed prior to the load() invocation. */ - basis::astring load(const basis::astring §ion, const basis::astring &entry, - const basis::astring &default_value); - - //! stores an integer value from the configuration store. - bool store(const basis::astring §ion, const basis::astring &entry, int value); - //! loads an integer value from the configuration store. - int load(const basis::astring §ion, const basis::astring &entry, int def_value); - - // the various methods below that operate on sections and entries might not - // be provided by all configurators. that is why there are empty default - // (or simplistic and slow) implementations provided below. - - virtual void sections(structures::string_array &list); - //!< retrieves the section names into "list". - - void section_set(structures::string_set &list); - //!< similar to above, but stores section names into a set. - /*!< this never needs to be overridden; it's simply a set instead - of an array. the real sections method is above for string_array. */ - - virtual bool delete_entry(const basis::astring & formal(section), - const basis::astring & formal(entry)) { return false; } - //!< eliminates the entry specified by the "section" and "entry" name. - - virtual bool delete_section(const basis::astring & formal(section) ) - { return false; } - //!< whacks the entire "section" specified. - - virtual bool section_exists(const basis::astring §ion); - //!< returns true if the "section" is found in the configurator. - /*!< the default implementation is quite slow; if there is a swifter means - for a particular type of configurator, then this should be overridden. */ - - virtual bool get_section(const basis::astring & formal(section), - structures::string_table & formal(found) ) { return false; } - //!< retrieves an entire "section", if supported by the derived object. - /*!< the symbol table "found" gets the entries from the "section". - see symbol_table.h for more details about string_tables. true is - returned if the section existed and its contents were put in "found". */ - - virtual bool put_section(const basis::astring & formal(section), - const structures::string_table & formal(to_store) ) { return false; } - //!< stores an entire "section" from the table in "to_store", if supported. - /*!< if any entries already exist in the "section", then they are - eliminated before the new entries are stored. true is returned if the - write was successful. */ - -private: - treatment_of_defaults _behavior; //!< records the treatment for defaults. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/ini_configurator.cpp b/core/library/configuration/ini_configurator.cpp deleted file mode 100644 index c65bdcca..00000000 --- a/core/library/configuration/ini_configurator.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/*****************************************************************************\ -* * -* Name : ini_configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ini_configurator.h" -#include "application_configuration.h" -#include "variable_tokenizer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -using namespace basis; -using namespace filesystem; -using namespace structures; - -namespace configuration { - -//#define DEBUG_INI_CONFIGURATOR - // uncomment for noisy version. - -const int MAXIMUM_LINE_INI_CONFIG = 16384; - -// a default we hope never to see in an ini file. -SAFE_STATIC_CONST(astring, ini_configurator::ini_str_fake_default, ("NoTomatoesNorPotatoesNorQuayle")) - -ini_configurator::ini_configurator(const astring &ini_filename, - treatment_of_defaults behavior, file_location_default where) -: configurator(behavior), - _ini_name(new filename), -#ifdef __UNIX__ - _parser(new ini_parser("", behavior)), -#endif - _where(where), - _add_spaces(false) -{ - name(ini_filename); // set name properly. -} - -ini_configurator::~ini_configurator() -{ - WHACK(_ini_name); -#ifdef __UNIX__ - WHACK(_parser); -#endif -} - -astring ini_configurator::name() const { return _ini_name->raw(); } - -void ini_configurator::refresh() -{ -#ifdef __UNIX__ - write_ini_file(); - WHACK(_parser); - _parser = new ini_parser("", behavior()); -#endif -} - -void ini_configurator::name(const astring &name) -{ - *_ini_name = name; - - bool use_appdir = true; - // true if we should put files where programs start for those filenames - // that don't include a directory name. - if (_where == OS_DIRECTORY) use_appdir = false; - if (_where == ALL_USERS_DIRECTORY) use_appdir = false; -#ifndef __WIN32__ - use_appdir = true; -#endif - // we must create the filename if they specified no directory at all. - if (!_ini_name->had_directory()) { - if (use_appdir) { - // this is needed in case there is an ini right with the file; our - // standard is to check there first. - *_ini_name = filename(application_configuration::application_directory(), - _ini_name->basename()); - } else if (!use_appdir && (_where == ALL_USERS_DIRECTORY) ) { - // when the location default is all users, we get that from the - // environment. for the OS dir choice, we leave out the path entirely. - directory::make_directory(environment::get("ALLUSERSPROFILE") - + "/" + application_configuration::software_product_name()); - *_ini_name = filename(environment::get("ALLUSERSPROFILE") - + "/" + application_configuration::software_product_name(), - _ini_name->basename()); - } - } -#ifdef __UNIX__ - // read in the file's contents. - read_ini_file(); -#endif -} - -void ini_configurator::sections(string_array &list) -{ - list = string_array(); - // open our ini file directly as a file. - byte_filer section8(*_ini_name, "rb"); - if (!section8.good()) return; // not a healthy file. - astring line_found; - // iterate through the lines of the ini file and see if we can't find a - // bunch of section names. - while (section8.read(line_found, MAXIMUM_LINE_INI_CONFIG) > 0) { - // is the line in the format "^[ \t]*\[\([^\]]+\)\].*$" ? - // if it is in that format, we add the matched \1 into our list. - line_found.strip_white_spaces(); - if (line_found[0] != '[') continue; // no opening bracket. skip line. - line_found.zap(0, 0); // toss opening bracket. - int close_brack_indy = line_found.find(']'); - if (negative(close_brack_indy)) continue; // no closing bracket. - line_found.zap(close_brack_indy, line_found.end()); - list += line_found; - } -} - -//hmmm: refactor section_exists to use the sections call, if it's faser? -bool ini_configurator::section_exists(const astring §ion) -{ -#ifdef __WIN32__ - string_table infos; - // heavy-weight call here... - return get_section(section, infos); -#else - return _parser->section_exists(section); -#endif -} - -#ifdef __UNIX__ -void ini_configurator::read_ini_file() -{ -#ifdef DEBUG_INI_CONFIGURATOR - FUNCDEF("read_ini_file"); -#endif - _parser->reset(""); // clear out our current contents. - byte_filer ini_file; - bool open_ret = ini_file.open(*_ini_name, "rb"); // simple reading. -#ifdef DEBUG_INI_CONFIGURATOR - if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name); - if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name); -#endif - if (!open_ret || !ini_file.good()) { - return; // failure. - } - int file_size = ini_file.length(); // get the file length. - // read the file. - astring contents(' ', file_size + 3); - int bytes_read = ini_file.read((abyte *)contents.observe(), file_size); - contents.zap(bytes_read + 1, contents.end()); - _parser->reset(contents); -} - -void ini_configurator::write_ini_file() -{ -#ifdef DEBUG_INI_CONFIGURATOR - FUNCDEF("write_ini_file"); -#endif - -//hmmm: just set dirty flag and use that for deciding whether to write. -//hmmm: future version, have a thread scheduled to write. - - // open filer with new mode for cleaning. - byte_filer ini_file; - bool open_ret = ini_file.open(*_ini_name, "wb"); - // open the file for binary read/write and drop previous contents. -#ifdef DEBUG_INI_CONFIGURATOR - if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name); - if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name); -#endif - if (!open_ret || !ini_file.good()) return; // failure. - - // output table's contents to text. - astring text; - _parser->restate(text, _add_spaces); - ini_file.write((abyte *)text.observe(), text.length()); -} -#endif //UNIX - -bool ini_configurator::delete_section(const astring §ion) -{ -#ifdef __WIN32__ - return put_profile_string(section, "", ""); -#else - // zap the section. - bool to_return = _parser->delete_section(section); - // schedule the file to write. - write_ini_file(); - return to_return; -#endif -} - -bool ini_configurator::delete_entry(const astring §ion, const astring &ent) -{ -#ifdef __WIN32__ - return put_profile_string(section, ent, ""); -#else - // zap the entry. - bool to_return = _parser->delete_entry(section, ent); - // schedule the file to write. - write_ini_file(); - return to_return; -#endif -} - -bool ini_configurator::put(const astring §ion, const astring &entry, - const astring &to_store) -{ -/// FUNCDEF("put"); - if (!to_store.length()) return delete_entry(section, entry); - else if (!entry.length()) return delete_section(section); - else if (!section.length()) return false; -#ifdef __WIN32__ - return put_profile_string(section, entry, to_store); -#else - // write the entry. - bool to_return = _parser->put(section, entry, to_store); - // schedule file write. - write_ini_file(); - return to_return; -#endif -} - -bool ini_configurator::get(const astring §ion, const astring &entry, - astring &found) -{ -#ifndef __WIN32__ - return _parser->get(section, entry, found); -#else - flexichar temp_buffer[MAXIMUM_LINE_INI_CONFIG]; - temp_buffer[0] = 0; - get_profile_string(section, entry, ini_str_fake_default(), - temp_buffer, MAXIMUM_LINE_INI_CONFIG - 1); - found = from_unicode_temp(temp_buffer); - return !(ini_str_fake_default() == found); -#endif -} - -bool ini_configurator::get_section(const astring §ion, string_table &info) -{ -/// FUNCDEF("get_section"); -#ifndef __WIN32__ - return _parser->get_section(section, info); -#else - info.reset(); - const int buffer_size = 200000; - - flexichar low_buff[buffer_size + 3]; - int read_len = GetPrivateProfileSection(to_unicode_temp(section.observe()), - low_buff, buffer_size - 1, to_unicode_temp(name())); - if (!read_len) return false; // assume the API means there was no section. - - low_buff[read_len] = '\1'; // signal beyond the end of the stuff. - low_buff[read_len + 1] = '\0'; // make sure we're still zero terminated. - - bool last_was_nil = false; - // this loop replaces all the embedded nils with separators to allow the - // variable_tokenizer to retrieve all the strings from the section. - for (int i = 0; i < read_len; i++) { - if (!low_buff[i] && last_was_nil) { - // termination condition; we got two nils in a row. - // this is just paranoia; the length should tell us. - break; - } else if (!low_buff[i]) { - low_buff[i] = '\1'; // replace with a separator. - last_was_nil = true; - } else last_was_nil = false; // reset the nil flag. - } - - // now convert to a simple astring. - astring buff = from_unicode_temp(low_buff); - int length = buff.length(); - buff.shrink(); - variable_tokenizer parser("\1", "="); - parser.parse(buff); - info = parser.table(); - return true; -#endif -} - -bool ini_configurator::put_section(const astring §ion, - const string_table &info) -{ -#ifdef __WIN32__ - variable_tokenizer parser("\1", "="); - parser.table() = info; - astring flat = parser.text_form(); - flat += "\1\1"; // add terminating guard. - int len = flat.length(); - for (int i = 0; i < len; i++) { - if (flat[i] == '\1') { - flat[i] = '\0'; - if (flat[i+1] == ' ') { - // if the space character is next, shift it before the nil to avoid - // keys with a preceding space. - flat[i] = ' '; - flat[i + 1] = '\0'; - } - } - } - return WritePrivateProfileSection(to_unicode_temp(section), - to_unicode_temp(flat), to_unicode_temp(name())); -#else - // write the section. - bool to_return = _parser->put_section(section, info); - // schedule file write. - write_ini_file(); - return to_return; -#endif -} - -#ifdef __WIN32__ -bool ini_configurator::put_profile_string(const astring §ion, - const astring &entry, const astring &to_store) -{ - return bool(WritePrivateProfileString(to_unicode_temp(section), - entry.length() ? (flexichar *)to_unicode_temp(entry) : NIL, - to_store.length() ? (flexichar *)to_unicode_temp(to_store) : NIL, - to_unicode_temp(name()))); -} - -void ini_configurator::get_profile_string(const astring §ion, - const astring &entry, const astring &default_value, - flexichar *return_buffer, int buffer_size) -{ - GetPrivateProfileString(section.length() ? - (flexichar *)to_unicode_temp(section) : NIL, - entry.length() ? (flexichar *)to_unicode_temp(entry) : NIL, - to_unicode_temp(default_value), - return_buffer, buffer_size, to_unicode_temp(name())); -} -#endif - -} //namespace. - - diff --git a/core/library/configuration/ini_configurator.h b/core/library/configuration/ini_configurator.h deleted file mode 100644 index ad99906d..00000000 --- a/core/library/configuration/ini_configurator.h +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef INI_CONFIGURATOR_CLASS -#define INI_CONFIGURATOR_CLASS - -/*****************************************************************************\ -* * -* Name : ini_configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" -#ifndef __WIN32__ - #include "ini_parser.h" - #include -#endif - -#include -#include -#include - -namespace configuration { - -//! Supports a configurator-based interface on text initialization files. - -class ini_configurator : public configurator -{ -public: - //! chooses where the ini file is located if no path to it is provided. - /*! the ini file being manipulated will be stored in either the same - directory as the program being executed (APPLICATION_DIRECTORY) or in the - directory where the operating system resides (OS_DIRECTORY). however, the - OS_DIRECTORY choice only really makes sense on windows. if the flag is - instead ALL_USERS_DIRECTORY, then the directory pointed at by the - $ALLUSERSPROFILE variable will be used on windows; otherwise, the default - is the same as for APPLICATION_DIRECTORY. */ - enum file_location_default { - APPLICATION_DIRECTORY, //!< config files live with application. - OS_DIRECTORY, //!< config files live in operating system directory. - ALL_USERS_DIRECTORY //!< config files live in the "all users" account. - }; - - ini_configurator(const basis::astring &ini_filename, - treatment_of_defaults behavior = RETURN_ONLY, - file_location_default where = ALL_USERS_DIRECTORY); - //!< creates an ini_configurator that stores entries into "ini_filename". - /*!< the ini config will have the "behavior" specified for how to handle - missing items. "where" dictates the file's location if no path is - specified as part of the "ini_filename". */ - - virtual ~ini_configurator(); - - DEFINE_CLASS_NAME("ini_configurator"); - - void refresh(); - //!< useful mainly on unix/linux, where the file is parsed and held in memory. - -//hmmm: where are: -// save_to_file() -// is_modified() -//? - - basis::astring name() const; - //!< observes the name of the file used for ini entries. - void name(const basis::astring &name); - //!< modifies the name of the file used for ini entries. - - virtual bool get(const basis::astring §ion, const basis::astring &entry, - basis::astring &found); - //!< implements the configurator retrieval function. - /*!< this returns true if the entry was present and stores it in - "found". */ - - virtual void sections(structures::string_array &list); - //!< retrieves the section names into "list". - - virtual bool section_exists(const basis::astring §ion); - //!< returns true if the "section" was found in the file. - /*!< NOTE: for an INI file, this is quite a heavy-weight call. */ - - virtual bool put(const basis::astring §ion, const basis::astring &entry, - const basis::astring &to_store); - //!< implements the configurator storage function. - - virtual bool delete_section(const basis::astring §ion); - //!< removes the entire "section" specified. - - virtual bool delete_entry(const basis::astring §ion, const basis::astring &entry); - //!< removes the entry specified by the "section" and "entry" name. - - virtual bool get_section(const basis::astring §ion, structures::string_table &info); - //!< reads the entire "section" into a table called "info". - /*!< on win95, this will fail if the section's data exceeds 32K. */ - - virtual bool put_section(const basis::astring §ion, const structures::string_table &info); - //!< writes a table called "info" into the "section" in the INI file. - /*!< any existing data for that section is wiped out. on win95, this will - fail if the section's data exceeds 32K. */ - - // dictates whether the stored entries will have spaces between '=' - // and the key name and value. only applicable on linux. - bool add_spaces() const { return _add_spaces; } - void add_spaces(bool add_them) { _add_spaces = add_them; } - -private: - filesystem::filename *_ini_name; //!< the file we're manipulating. -#ifdef __UNIX__ - ini_parser *_parser; //!< used for real storage and parsing. -#endif - file_location_default _where; //!< where to find and store the file. - bool _add_spaces; //!< tracks whether we're adding spaces around equals. - -#ifdef __WIN32__ - bool put_profile_string(const basis::astring §ion, const basis::astring &entry, - const basis::astring &to_store); - //!< encapsulates windows' ini storage method. - void get_profile_string(const basis::astring §ion, const basis::astring &entry, - const basis::astring &default_value, basis::flexichar *return_buffer, - int buffer_size); - //!< encapsulates windows' ini retrieval method. -#endif -#ifdef __UNIX__ - void read_ini_file(); - //!< reads the INI file's contents into memory. - void write_ini_file(); - //!< store the current contents into the INI file. -#endif - - // not to be called. - ini_configurator(const ini_configurator &); - ini_configurator &operator =(const ini_configurator &); - - static const basis::astring &ini_str_fake_default(); -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/ini_parser.cpp b/core/library/configuration/ini_parser.cpp deleted file mode 100644 index bb881c80..00000000 --- a/core/library/configuration/ini_parser.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/*****************************************************************************\ -* * -* Name : ini_parser * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ini_parser.h" -#include "table_configurator.h" -#include "variable_tokenizer.h" - -#include -#include -#include -#include -#include -#include - -//#define DEBUG_INI_PARSER - // uncomment for noisy version. - -#undef LOG -#ifdef DEBUG_INI_PARSER - #define LOG(to_print) printf("%s\n", astring(to_print).s()) -#else - #define LOG(a) {} -#endif - -////////////// - -using namespace basis; -using namespace structures; -using namespace textual; -//using namespace ; - -namespace configuration { - -//algorithm: -// gather section until next section definition or end of file. -// parse the section with variable_tokenizer. -// eat that out of the string. -// repeat. - -ini_parser::ini_parser(const astring &to_parse, treatment_of_defaults behavior) -: table_configurator(behavior), - _well_formed(false), - _preface(new astring) -{ - reset(to_parse); -} - -ini_parser::~ini_parser() -{ - WHACK(_preface); -} - -void ini_parser::chow_through_eol(astring &to_chow) -{ - while (to_chow.length()) { - if (parser_bits::is_eol(to_chow[0])) { - // zap all carriage return type chars now that we found one. - while (to_chow.length() && parser_bits::is_eol(to_chow[0])) { - *_preface += to_chow[0]; - to_chow.zap(0, 0); - } - return; // mission accomplished. - } - *_preface += to_chow[0]; - to_chow.zap(0, 0); - } -} - -/* -//this is a super expensive operation... -// it would be better to have the parser be a bit more intelligent. -void strip_blank_lines(astring &to_strip) -{ - bool last_was_ret = false; - for (int i = 0; i < to_strip.length(); i++) { - if (parser_bits::is_eol(to_strip[i])) { - if (last_was_ret) { - // two in a row; now that's bogus. - to_strip.zap(i, i); - i--; // skip back. - continue; - } - last_was_ret = true; - to_strip[i] = '\n'; // make sure we know which type to look for. - } else { - if (last_was_ret && parser_bits::white_space(to_strip[i])) { - // well, the last was a return but this is white space. that's also - // quite bogus. - to_strip.zap(i, i); - i--; // skip back. - continue; - } - last_was_ret = false; - } - } -} -*/ - -void ini_parser::reset(const astring &to_parse) -{ - _well_formed = false; - table_configurator::reset(); // clean out existing contents. - _preface->reset(); // set the preface string back to nothing. - add(to_parse); -} - -void ini_parser::add(const astring &to_parse) -{ - astring parsing = to_parse; -// strip_blank_lines(parsing); - _preface->reset(); // set the preface string back to nothing. - while (parsing.length()) { - astring section_name; - bool found_sect = parse_section(parsing, section_name); - if (!found_sect) { - // the line is not a section name. toss it. - chow_through_eol(parsing); - continue; // try to find another section name. - } - // we got a section. yee hah. - int next_sect = 0; - for (next_sect = 0; next_sect < parsing.length(); next_sect++) { -// LOG(astring("[") + astring(parsing[next_sect], 1) + "]"); - if (parser_bits::is_eol(parsing[next_sect])) { - // we found the requisite return; let's see if a section beginning - // is just after it. we know nothing else should be, since we stripped - // out the blank lines and blanks after CRs. - if (parsing[next_sect + 1] == '[') { - // aha, found the bracket that should be a section start. - break; // done seeking next section beginning. - } - } - } - // skip back one if we hit the end of the string. - if (next_sect >= parsing.length()) next_sect--; - // now grab what should be all values within a section. - LOG(a_sprintf("bounds are %d to %d, string len is %d.", 0, next_sect, - parsing.length())); - astring sect_parsing = parsing.substring(0, next_sect); - LOG(astring("going to parse: >>") + sect_parsing + "<<"); - parsing.zap(0, next_sect); - variable_tokenizer section_reader("\n", "="); - section_reader.set_comment_chars(";#"); - section_reader.parse(sect_parsing); - LOG(astring("read: ") + section_reader.text_form()); - merge_section(section_name, section_reader.table()); - } - _well_formed = true; -} - -void ini_parser::merge_section(const astring §ion_name, - const string_table &to_merge) -{ - if (!section_exists(section_name)) { - // didn't exist yet, so just plunk it in. - put_section(section_name, to_merge); - return; - } - - // since the section exists, we just write the individual entries from the - // new section. they'll stamp out any old values. - for (int i = 0; i < to_merge.symbols(); i++) - put(section_name, to_merge.name(i), to_merge[i]); -} - -bool ini_parser::parse_section(astring &to_parse, astring §ion_name) -{ - section_name = ""; // reset the section. - - // we have a simple state machine here... - enum states { - SEEKING_OPENING_BRACKET, // looking for the first bracket. - EATING_SECTION_NAME // got a bracket, now getting section name. - }; - states state = SEEKING_OPENING_BRACKET; - - // zip through the string trying to find a valid section name. - for (int i = 0; i < to_parse.length(); i++) { - char curr = to_parse[i]; - LOG(astring("<") + astring(curr, 1) + ">"); - switch (state) { - case SEEKING_OPENING_BRACKET: - // we're looking for the first bracket now... - if (parser_bits::white_space(curr)) continue; // ignore white space. - if (curr != '[') return false; // argh, bad characters before bracket. - state = EATING_SECTION_NAME; // found the bracket. - break; - case EATING_SECTION_NAME: - // we're adding to the section name now... - if (curr == ']') { - // that's the end of the section name. - to_parse.zap(0, i); // remove what we saw. -//should we take out to end of line also? -//eventually up to eol could be kept as a comment? - return true; - } - section_name += curr; // add a character to the name. - break; - default: - //LOG("got to unknown case in section parser!"); - return false; - } - } - // if we got to here, the section was badly formed... the whole string was - // parsed through but no conclusion was reached. - return false; -} - -bool ini_parser::restate(astring &new_ini, bool add_spaces) -{ - new_ini = *_preface; // give it the initial text back again. - string_array sects; - sections(sects); - for (int i = 0; i < sects.length(); i++) { - new_ini += astring("[") + sects[i] + "]" + parser_bits::platform_eol_to_chars(); - string_table tab; - if (!get_section(sects[i], tab)) continue; // serious error. - tab.add_spaces(add_spaces); - new_ini += tab.text_form(); - } - return true; -} - -} //namespace. - - diff --git a/core/library/configuration/ini_parser.h b/core/library/configuration/ini_parser.h deleted file mode 100644 index fb061dab..00000000 --- a/core/library/configuration/ini_parser.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef INI_PARSER_CLASS -#define INI_PARSER_CLASS - -/*****************************************************************************\ -* * -* Name : ini_parser * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "table_configurator.h" - -namespace configuration { - -//! Parses strings in the fairly well-known INI file format. -/*! - Description of INI file format: - - The format expected in this parser for initialization files allows for - three types of entries. These are section headers, comments and value - definitions. - Section headers define the start of a list of value definitions. A - section header is a name in brackets, like [startup]. - A comment is a string of text that will be ignored. Comments are preceded - by an octothorpe ('#') or a semicolon (';'). The parser will keep comments - roughly in the same places they were found in the string that was parsed. - A comment is allowed to follow a section header on the same line. - Value definitions are a pair of strings separated by an equality operator - ('='). The left side of the value definition is referred to here as the - variable's name while the right side is referred to as the variable's value. - Note that any line which is not a comment or a section header is considered - implicitly to be a value definition, even if it does not contain an equals - operator. This is required for parsing certain INI files that don't follow - the established format. Such lines will have an empty string for their - value. - White space (either tab characters or space characters) are allowed before - and after any of these constructs. Spaces may also exist before and after - the equals operator of a value definition, but once the value starts, any - white space is considered part of the value. Trailing white space is not - considered part of a variable name, but white space between the characters - before the equals operator is signficant. Any number of carriage returns - can separate the lines of the INI file. - - Here is an example of a valid INI file: - @code - # Initialization file for Frootnik Corp. - - [common] ; all of our programs use these. - magnification=1 - - text_color=puce - font=atavata - ;;; see font/color document in readme.txt. - - [sourceburger] ; sourceburger application specific settings - - crashnow = 0 - crashlater = 1 - get clue=0 - windowtitle = Source Burger 5000 - - danger will robinson - @endcode -*/ - -class ini_parser : public table_configurator -{ -public: - ini_parser(const basis::astring &to_parse, - treatment_of_defaults behavior = RETURN_ONLY); - //!< constructs an ini_parser by parsing entries out of "to_parse". - /*!< after construction, the success of parsing can be checked using - well_formed(). */ - - ~ini_parser(); - - void reset(const basis::astring &to_parse); - //!< drops any existing information and processes the string "to_parse". - - void add(const basis::astring &to_parse); - //!< merges items parsed from "to_parse" into the current set. - /*!< processes the string "to_parse" as in the reset() method but adds - any new sections found to our configuration. if sections are found with - the same names, then the values found in "to_parse" override the ones - already listed. */ - - bool well_formed() const { return _well_formed; } - //!< returns true if the ini file's contents were in the format expected. - - bool restate(basis::astring &new_ini, bool add_spaces = false); - //!< stores a cleaned version of the internal state into "new_ini". - /*!< if "add_spaces" is true, then the entries will be in the form of - 'x = y' rather than 'x=y'. */ - - void merge_section(const basis::astring §ion_name, const structures::string_table &to_merge); - //!< merges the table "to_merge" into the "section_name". - /*!< any new values from "to_merge" that are not found in the section with - "section_name" in "this" object are added and any existing values will be - replaced. */ - -private: - bool _well_formed; //!< true if the ini file had a valid format. - basis::astring *_preface; //!< information that comes before the first section. - - void chow_through_eol(basis::astring &to_chow); - //!< eats up to an EOL character but adds the text to our preface string. - - bool parse_section(basis::astring &to_parse, basis::astring §ion_name); - //!< looks for a section name in the string "to_parse". - /*!< true is returned on success; success means that a "section_name" was - found and that "to_parse" has been destructively eaten to remove it. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/ini_roller.cpp b/core/library/configuration/ini_roller.cpp deleted file mode 100644 index 36110081..00000000 --- a/core/library/configuration/ini_roller.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/*****************************************************************************\ -* * -* Name : ini_roller * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ini_roller.h" - -#include -#include -#include -///#include - -using namespace basis; -using namespace structures; - -namespace configuration { - -//#define DEBUG_ID_GRANTING - // uncomment if you want verbose granting of unique ids. - -const int ID_FACTOR = 28; - // this many ids are grabbed at once for eventual issuance. - -ini_roller::ini_roller(configurator &config, const astring §ion, - const astring &entry, int min, int max) -: _ini(config), - _ids(new int_roller(min, max)), - _section(new astring(section)), - _entry(new astring(entry)), - _lock(new mutex) -{ - int current = _ini.load(section, entry, min); - _ids->set_current(current); - // make our first requisition of ids. we start here rather than playing - // games with the next_id function. - _ini.store(section, entry, _ids->current() + ID_FACTOR); -} - -ini_roller::~ini_roller() -{ - // force the id to be past what we've allocated, but not too far past. - _ini.store(*_section, *_entry, _ids->current() + 1); - WHACK(_ids); - WHACK(_section); - WHACK(_entry); - WHACK(_lock); -} - -int ini_roller::current_id() const -{ - auto_synchronizer l(*_lock); - return _ids->current(); -} - -int ini_roller::next_id() -{ -#ifdef DEBUG_ID_GRANTING - FUNCDEF("next_id"); -#endif - auto_synchronizer l(*_lock); - int to_return = _ids->current(); - - // this uses a relaxed id issuance policy; the id that's in the INI - // file is only updated when we run out of the range that we allocate for it. - // the roller's current value is used whenever issuing an id, but next_id() - // is always called before that id is actually issued. - - if ( (_ids->current() < _ids->maximum() - 2) - && (_ids->current() % ID_FACTOR) ) { - // no id range grabbing needed yet and no rollover. - _ids->next_id(); -#ifdef DEBUG_ID_GRANTING - LOG(astring(astring::SPRINTF, "standard id issue: %d.", to_return)); -#endif - return to_return; - } - - // now we need to allocate a new range of ids... and store in ini. - int new_range = to_return + ID_FACTOR; -#ifdef DEBUG_ID_GRANTING - LOG(astring(astring::SPRINTF, "finding next range, new start in ini " - "is: %d.", new_range)); -#endif - // if the id wraps around, reset it. - if ( (new_range < 0) || (new_range >= _ids->maximum()) ) - new_range = ID_FACTOR; -#ifdef DEBUG_ID_GRANTING - LOG(astring(astring::SPRINTF, "after check, new ini id is: %d.", - new_range)); -#endif - _ini.store(*_section, *_entry, new_range); - // set the next stored id to the block above where we're using. - _ids->next_id(); // jump to the next one in the range. -#ifdef DEBUG_ID_GRANTING - LOG(astring(astring::SPRINTF, "after store, id is: %d.", to_return)); -#endif - return to_return; -} - -} //namespace. - diff --git a/core/library/configuration/ini_roller.h b/core/library/configuration/ini_roller.h deleted file mode 100644 index f41469ea..00000000 --- a/core/library/configuration/ini_roller.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef INI_ROLLER_CLASS -#define INI_ROLLER_CLASS - -/*****************************************************************************\ -* * -* Name : ini_roller * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" - -#include -#include -#include - -namespace configuration { - -//! Implements an id generator that interacts with a configuration file. -/*! - This provides an int_roller (which provides rolling ids given a range to - issue them from) that is stored in a configurator. The instantiated - object is the real source of the ids, but the configurator file is - periodically updated to reflect the current id state. If a program - is restarted later, then it will start using ids that it had not already - issued in its last run, as long as the configurator is a persistent object. - Note that the range of ids had better be quite large; otherwise the program - could still have live entries under an id that is about to be reissued - due to wrap-around. -*/ - -class ini_roller : public virtual basis::root_object -{ -public: - ini_roller(configurator &config, const basis::astring §ion, - const basis::astring &entry, int min, int max); - //!< creates a roller that updates "config" with the current id value. - /*!< the updates are not continuous, but are done periodically to avoid - constantly writing to the "config". the "section" and "entry" dictate - where the entry is saved in the "config". the "min" and "max" provide the - range of the id, where it will start at "min", increment by one until it - reaches at most "max", and then it will start back at "min" again. note - that "config" must exist for the duration of the ini_roller. */ - - virtual ~ini_roller(); - - DEFINE_CLASS_NAME("ini_roller"); - - int next_id(); - //!< returns the next number to be issued. - - int current_id() const; - //!< returns the current id; this is the one that was last issued. - -private: - configurator &_ini; //!< provides access to the ini file. - structures::int_roller *_ids; //!< the current id number is managed here. - basis::astring *_section; //!< remembers the right section for our id entry. - basis::astring *_entry; //!< remembers the entry name. - basis::mutex *_lock; //!< keeps us thread safe. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/makefile b/core/library/configuration/makefile deleted file mode 100644 index 1ac89e79..00000000 --- a/core/library/configuration/makefile +++ /dev/null @@ -1,11 +0,0 @@ -include cpp/variables.def - -PROJECT = configuration -TYPE = library -SOURCE = application_configuration.cpp config_watcher.cpp configurator.cpp configlet.cpp \ - configuration_list.cpp ini_configurator.cpp ini_parser.cpp ini_roller.cpp \ - section_manager.cpp system_values.cpp table_configurator.cpp variable_tokenizer.cpp -TARGETS = configuration.lib - -include cpp/rules.def - diff --git a/core/library/configuration/section_manager.cpp b/core/library/configuration/section_manager.cpp deleted file mode 100644 index eabf5d76..00000000 --- a/core/library/configuration/section_manager.cpp +++ /dev/null @@ -1,113 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : section_manager * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "section_manager.h" - -#include -#include -#include -#include - -using namespace basis; -using namespace configuration; -using namespace structures; - -namespace configuration { - -section_manager::section_manager(configurator &config, - const astring &toc_title, const astring &header_prefix) -: _config(config), - _toc_title(new astring(toc_title)), - _section_prefix(new astring(header_prefix)) -{ -} - -section_manager::~section_manager() -{ - WHACK(_toc_title); - WHACK(_section_prefix); -} - -bool section_manager::get_toc(string_table &toc) -{ return _config.get_section(*_toc_title, toc); } - -bool section_manager::get_section_names(string_array §ions) -{ - sections.reset(); // clean up the array they gave us. - string_table toc; - if (!get_toc(toc)) return false; - for (int i = 0; i < toc.symbols(); i++) sections += toc.name(i); - return true; -} - -bool section_manager::section_exists(const astring §ion_name) -{ - if (!section_name) return false; // empty names are invalid. - return _config.load(*_toc_title, section_name, "").t(); -} - -astring section_manager::make_section_heading(const astring §ion) -{ return *_section_prefix + section; } - -bool section_manager::add_section(const astring §ion_name, - const string_table &to_add) -{ - if (!section_name) return false; // empty names are invalid. - if (section_exists(section_name)) return false; - // write the name into the table of contents. - astring section_value = "t"; // place-holder for section entries. - if (!to_add.symbols()) - section_value = ""; // nothing to write; delete the section entry. - if (!_config.put(*_toc_title, section_name, section_value)) - return false; - // create the appropriate header for the new section. - astring header = make_section_heading(section_name); - // if there aren't any elements, the put_section should just wipe out - // the entire section. - return _config.put_section(header, to_add); -} - -bool section_manager::replace_section(const astring §ion_name, - const string_table &replacement) -{ - if (!section_name) return false; // empty names are invalid. - if (!section_exists(section_name)) return false; - if (!zap_section(section_name)) return false; - if (!replacement.symbols()) return true; // nothing to write. - return add_section(section_name, replacement); -} - -bool section_manager::zap_section(const astring §ion_name) -{ - if (!section_name) return false; // empty names are invalid. - astring header = make_section_heading(section_name); - if (!_config.delete_section(header)) return false; - return _config.delete_entry(*_toc_title, section_name); -} - -bool section_manager::find_section(const astring §ion_name, - string_table &found) -{ - if (!section_name) return false; // empty names are invalid. - if (!section_exists(section_name)) return false; - astring header = make_section_heading(section_name); - return _config.get_section(header, found); -} - -} //namespace. - - diff --git a/core/library/configuration/section_manager.h b/core/library/configuration/section_manager.h deleted file mode 100644 index dea180c4..00000000 --- a/core/library/configuration/section_manager.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef SECTION_MANAGER_CLASS -#define SECTION_MANAGER_CLASS - -/*****************************************************************************\ -* * -* Name : section_manager * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" - -#include - -namespace configuration { - -//! Tracks a collection of related configurations in a configurator. -/*! - If there is a set of items that need to be stored in a configurator, where - each item has its own configuration section, then this object can help out. - It manages a collection of uniquely named sections in a configurator object - and provides a table of contents (TOC) feature for the names of the sections. - Each item lives in its own distinct section but the whole set can be - operated on as one entity. -*/ - -class section_manager : public virtual basis::nameable -{ -public: - section_manager(configurator &config, const basis::astring &toc_title, - const basis::astring &header_prefix); - //!< creates a section_manager that uses the "config" for storage. - /*!< the "toc_title" is the name of the section to be used for storing the - table of contents for the managed configuration items. the "header_prefix" - will be prepended to the names of each section to facilitate locating them. - for example, if the "toc_title" is "client channels" and the "header_prefix" - is "CliChan__", then the resulting configuration might look similar to the - following: @code - [client channels] - joe - ted - [CliChan__joe] - port=58 - [CliChan__ted] - address=13.8.92.4 - auth=primary - @endcode */ - - ~section_manager(); - - DEFINE_CLASS_NAME("section_manager"); - - bool section_exists(const basis::astring §ion_name); - //!< returns true if the section called "section_name" exists in the config. - - bool get_section_names(structures::string_array §ions); - //!< loads the "sections" array with all section names. - /*!< this comes from our table of contents. true is returned if there - were any names to load. */ - - bool add_section(const basis::astring §ion_name, const structures::string_table &to_add); - //!< stores a new section for "section_name" using the table "to_add". - /*!< this will fail if the section already exists. */ - - bool replace_section(const basis::astring §ion, const structures::string_table &replacement); - //!< replaces the contents of "section" with the "replacement" table. - /*!< this will fail if the section does not already exist. */ - - bool zap_section(const basis::astring §ion_name); - //!< removes the data for "section_name" from both the config and TOC. - /*!< this will fail if the section is not present. */ - - bool find_section(const basis::astring §ion_name, structures::string_table &found); - //!< loads the data from "section_name" into the table "found". - /*!< this fails if the section doesn't exist or if the section's contents - couldn't be detokenized into a table of name/value pairs. */ - - configurator &config() { return _config; } - //!< allows access to the configurator we operate on. - /*!< getting single items from the config will be signficantly faster - using it directly; the make_section_heading() method can be used to locate - the proper section. */ - - bool get_toc(structures::string_table &toc); - //!< reads the table of contents into "toc". - - basis::astring make_section_heading(const basis::astring §ion); - //!< provides the appropriate heading string for the "section" name. - /*!< this can be used to find entries using the config(). */ - -private: - configurator &_config; //!< the configuration object we interact with. - basis::astring *_toc_title; //!< the table of contents' section name. - basis::astring *_section_prefix; //!< prefix attached to the name of the section. - - section_manager(const section_manager &); //!< currently forbidden. - section_manager &operator =(const section_manager &); - //!< currently forbidden. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/system_values.cpp b/core/library/configuration/system_values.cpp deleted file mode 100644 index c82b515e..00000000 --- a/core/library/configuration/system_values.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/*****************************************************************************\ -* * -* Name : system_values * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ini_configurator.h" -#include "system_values.h" - -#include -#include -#include -#include -#include - -using namespace algorithms; -using namespace basis; -using namespace structures; -using namespace textual; - -namespace configuration { - -const int MAX_VALUE_BITS = 8; // we provide 2^n slots in hash. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -////////////// - -class value_record -{ -public: - astring _name; // the name of the value. - astring _descrip; // the description of the value. - astring _location; // the file defining the value. - - value_record(const astring &name = astring::empty_string(), - const astring &description = astring::empty_string(), - const astring &location = astring::empty_string()) - : _name(name), _descrip(description), _location(location) {} -}; - -////////////// - -class system_values_lookup_list : public int_hash -{ -public: - system_values_lookup_list() : int_hash(MAX_VALUE_BITS) {} - - // finds the symbolic "name" in the table, which is not as efficient as - // lookin up integers. - value_record *text_find(const astring &name, int &value) { - // scoot across all of the ids. - const int_set &cids = ids(); - for (int i = 0; i < cids.elements(); i++) { - int current_id = cids[i]; - value_record *curr = find(current_id); - if (!curr) { -//serious error. - continue; - } - if (curr->_name == name) { - // this is a match to the name they were seeking. - value = current_id; - return curr; - } - } - return NIL; - } -}; - -////////////// - -system_values::system_values(const astring §ion_tag) -: _tag(new astring(section_tag)), - _list(new system_values_lookup_list), - _file(new astring(DEFAULT_MANIFEST)) -{ -// FUNCDEF("constructor"); - open_values(); -} - -system_values::~system_values() -{ - WHACK(_list); - WHACK(_tag); - WHACK(_file); -} - -const char *system_values::DEFAULT_MANIFEST = "manifest.txt"; - // this is the default manifest and it is expected to live right in - // the folder where the applications are. - -bool system_values::use_other_manifest(const astring &manifest_file) -{ - *_file = manifest_file; - return open_values(); -} - -const char *system_values::OUTCOME_VALUES() { return "DEFINE_OUTCOME"; } - -const char *system_values::FILTER_VALUES() { return "DEFINE_FILTER"; } - -const char *system_values::EVENT_VALUES() { return "DEFINE_EVENT"; } - -bool system_values::open_values() -{ -// FUNCDEF("open_values"); - ini_configurator ini(*_file, ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY); - - string_table full_section; - bool got_section = ini.get_section(*_tag, full_section); - if (!got_section) return false; // nothing there to look up. - for (int i = 0; i < full_section.symbols(); i++) { - - string_array items; - list_parsing::parse_csv_line(full_section.name(i), items); - if (items.length() < 4) { - continue; - } - - value_record *entry = new value_record(items[0], items[2], items[3]); - int value = items[1].convert(0); - _list->add(value, entry); - } - - return true; -} - -#define SV_EOL parser_bits::platform_eol_to_chars() - -//hmmm: it might be nice to have an alternate version sorted by name... - -astring system_values::text_form() const -{ - int_set cids = _list->ids(); - - if (!_tag->equal_to("DEFINE_OUTCOME")) { - // sort the list in identifier order. - shell_sort(cids.access(), cids.elements()); - } else { - // sort the list in reverse identifier order, since zero is first - // for outcomes and then they go negative. - shell_sort(cids.access(), cids.elements(), true); - } - - astring to_return("values for "); - to_return += *_tag; - to_return += SV_EOL; - for (int i = 0; i < cids.elements(); i++) { - int current_id = cids[i]; - value_record *curr = _list->find(current_id); - if (!curr) { -//serious error. - continue; - } - to_return += a_sprintf("%d: ", current_id); - to_return += curr->_name + " \"" + curr->_descrip + "\" from " - + curr->_location; - to_return += SV_EOL; - } - return to_return; -} - -bool system_values::lookup(int value, astring &symbolic_name, - astring &description, astring &file_location) -{ - value_record *found = _list->find(value); - if (!found) return false; - symbolic_name = found->_name; - description = found->_descrip; - file_location = found->_location; - return true; -} - -bool system_values::lookup(const astring &symbolic_name, int &value, - astring &description, astring &file_location) -{ - value_record *found = _list->text_find(symbolic_name, value); - if (!found) return false; - description = found->_descrip; - file_location = found->_location; - return true; -} - -int system_values::elements() const { return _list->ids().elements(); } - -bool system_values::get(int index, astring &symbolic_name, int &value, - astring &description, astring &file_location) -{ - bounds_return(index, 0, _list->ids().elements() - 1, false); // bad index. - value = _list->ids()[index]; - return lookup(value, symbolic_name, description, file_location); -} - -} //namespace. - - diff --git a/core/library/configuration/system_values.h b/core/library/configuration/system_values.h deleted file mode 100644 index 02c11b7f..00000000 --- a/core/library/configuration/system_values.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef SYSTEM_VALUES_CLASS -#define SYSTEM_VALUES_CLASS - -/*****************************************************************************\ -* * -* Name : system_values * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -//#include - -namespace configuration { - -// forward. -class system_values_lookup_list; - -//! This class provides a way to look up generated values used in the code base. -/*! - The type of value here includes outcomes, events and filters so far. These - items are reported in the build manifest that is included with every release - of the compiled software. The system_values class provides a lookup - interface to the extensible system of unique identifiers which is mapped by - the manifest file. The manifest file is processed like an initialization - file to retrieve the descriptions and names of symbols when given their - numerical identifiers. -*/ - -class system_values : public virtual basis::root_object -{ -public: - system_values(const basis::astring §ion_tag); - //!< provides a lookup on values found in the section named "section_tag". - /*!< the "section_tag" indicates what kind of asset is being looked up in - the manifest. it is always assumed that the manifest file is the main - one defined here in DEFAULT_MANIFEST. it must be a file created by - the value_tagger during the indexing of all value assets defined in the - build. - the valid values for the section names so far are "DEFINE_ OUTCOME", - "DEFINE_ FILTER" and "DEFINE_ EVENT" (with no spaces), but it is better - to use the defined "VALUES" methods below (such as OUTCOME_VALUES()). - outcomes are used by functions to describe how the operation completed. - filters are used to enable different types of logging. events are sent - between information source and sink to indicate the occurrence of an - activity. */ - - virtual ~system_values(); - - DEFINE_CLASS_NAME("system_values"); - - // these provide symbolic versions of the section tag used in the - // constructor. these are preferable to using the string constants - // directly. - static const char *OUTCOME_VALUES(); - //!< values that define the outcomes of operations. - static const char *FILTER_VALUES(); - //!< values that define filters used in logging. - static const char *EVENT_VALUES(); - //!< values that define event objects used in the program. - - static const char *DEFAULT_MANIFEST; - //!< the default manifest file. - /*!< it is expected to live in the folder where the applications are. */ - - bool use_other_manifest(const basis::astring &manifest_file); - //!< supports using a different manifest file than the default. - /*!< the values will now come from the "manifest_file" instead of the - default manifest name shipped with the software release. */ - - virtual basis::astring text_form() const; - //!< shows all items in the table. - - bool lookup(int value, basis::astring &symbolic_name, basis::astring &description, - basis::astring &file_location); - //!< locates a "value" and finds its name, description and location. - /*!< this looks up one of the enumerated values defined for our type of - value. true is returned if the value is meaningful. the "symbolic_name" - is set to the item's name as used inside the source code (i.e., its enum - member's name), the "description" is set to the full textual description - of what that value means, and the "file_location" is set to the name of - the source file that defines the value. */ - - bool lookup(const basis::astring &symbolic_name, int &value, basis::astring &description, - basis::astring &file_location); - //!< similar to the above lookup, but seeks on the "symbolic_name". - /*!< this lookup tries to associate from the textual name of the value - in order to find the integer actual "value" of it. the textual - "description" and "file_location" for that item are also included. */ - - int elements() const; - //!< returns how many items are listed for the types of values specified. - - bool get(int index, basis::astring &symbolic_name, int &value, - basis::astring &description, basis::astring &file_location); - //!< accesses the "index"th item in the list. - -private: - basis::astring *_tag; //!< the name of our section. - system_values_lookup_list *_list; //!< the values and text that we found. - basis::astring *_file; //!< the manifest filename. - - bool open_values(); //!< retrieves the information from the file. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/table_configurator.cpp b/core/library/configuration/table_configurator.cpp deleted file mode 100644 index 692ed0d0..00000000 --- a/core/library/configuration/table_configurator.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/*****************************************************************************\ -* * -* Name : table_configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "table_configurator.h" - -#include -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace configuration { - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -class table_o_string_tables : public symbol_table -{ -public: -}; - -////////////// - -table_configurator::table_configurator(treatment_of_defaults behavior) -: configurator(behavior), - _real_table(new table_o_string_tables) -{} - -table_configurator::table_configurator(const table_configurator &to_copy) -: configurator(to_copy.behavior()), - _real_table(new table_o_string_tables) -{ *this = to_copy; } - -table_configurator::~table_configurator() -{ - WHACK(_real_table); -} - -table_configurator &table_configurator::operator = - (const table_configurator &to_copy) -{ - if (this == &to_copy) return *this; - reset(); - string_array sects; - const_cast(to_copy).sections(sects); - for (int sectindy = 0; sectindy < sects.length(); sectindy++) { - // every entry in the current section gets added to our current config. - astring curr_section = sects[sectindy]; - string_table entries; - const_cast(to_copy).get_section(curr_section, entries); - put_section(curr_section, entries); - } - - return *this; -} - -void table_configurator::reset() { _real_table->reset(); } - -bool table_configurator::section_exists(const astring §ion) -{ return !!_real_table->find(section); } - -void table_configurator::sections(string_array &to_fill) -{ - to_fill.reset(); - for (int i = 0; i < _real_table->symbols(); i++) - to_fill += _real_table->name(i); -} - -bool table_configurator::delete_section(const astring §ion) -{ return _real_table->whack(section) == common::OKAY; } - -bool table_configurator::delete_entry(const astring §ion, - const astring &ent) -{ - string_table *sect = _real_table->find(section); - if (!sect) return false; - return sect->whack(ent) == common::OKAY; -} - -bool table_configurator::put(const astring §ion, - const astring &entry, const astring &to_store) -{ - if (!to_store.length()) return delete_entry(section, entry); - else if (!entry.length()) return delete_section(section); - string_table *sect = _real_table->find(section); - if (!sect) { - // none exists yet, so add one. - _real_table->add(section, string_table()); - sect = _real_table->find(section); - } - sect->add(entry, to_store); - return true; -} - -bool table_configurator::get(const astring §ion, - const astring &entry, astring &found) -{ - found = ""; - string_table *sect = _real_table->find(section); - if (!sect) return false; - astring *looked = sect->find(entry); - if (!looked) return false; - found = *looked; - return true; -} - -/* -scavenge? -bool is_comment(char to_check, const char *comment_list) -{ - int len = int(strlen(comment_list)); - for (int i = 0; i < len; i++) { - if (to_check == comment_list[i]) - return true; - } - return false; -} -*/ - -/* scavenge? -//hmmm: could we move the commented and clean_comments methods into -// parser bits? -// yes! we should move those; they are no longer used here! - -bool table_configurator::commented(const astring &to_check, - const char *comment_list) -{ - for (int i = 0; i < to_check.length(); i++) { - if (white_space(to_check[i])) - continue; // skip spaces. - if (is_comment(to_check[i], comment_list)) - return true; // started with a comment. - return false; // we had our chance for a comment, but that wasn't it. - } - return false; -} - -astring table_configurator::clean_comments(const astring &to_clean, - const char *comment_list) -{ - FUNCDEF("clean_comments"); -//LOG(astring("clean in with: ") + to_clean); - astring to_return(' ', to_clean.length()); // make a long enough string. - to_return.reset(); // keep allocated buffer size, but throw out contents. - for (int i = 0; i < to_clean.length(); i++) { - if (is_comment(to_clean[i], comment_list)) { - // here we go; the rest is commented out. - break; - } - to_return += to_clean[i]; - } -//LOG(astring("clean out with: ") + to_return); - return to_return; -} -*/ - -bool table_configurator::get_section(const astring §ion, - string_table &info) -{ -/// FUNCDEF("get_section"); - info.reset(); - string_table *sect = _real_table->find(section); - if (!sect) return false; - for (int i = 0; i < sect->symbols(); i++) - info.add(sect->name(i), (*sect)[i]); - return true; -} - -bool table_configurator::put_section(const astring §ion, - const string_table &info) -{ -/// FUNCDEF("put_section"); - string_table *sect = _real_table->find(section); - if (!sect) { - // none exists yet, so add one. - _real_table->add(section, string_table()); - sect = _real_table->find(section); - } - *sect = info; - return true; -} - -} //namespace. - diff --git a/core/library/configuration/table_configurator.h b/core/library/configuration/table_configurator.h deleted file mode 100644 index 4beaee00..00000000 --- a/core/library/configuration/table_configurator.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef TABLE_CONFIGURATOR_CLASS -#define TABLE_CONFIGURATOR_CLASS - -/*****************************************************************************\ -* * -* Name : table_configurator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configurator.h" - -#include - -namespace configuration { - -// forward. -class table_o_string_tables; - -//! Supports the configurator interface using a collection of string tables. - -class table_configurator : public virtual configurator -{ -public: - table_configurator(treatment_of_defaults behavior = AUTO_STORE); - //!< Constructor just needs to know what to do for missing items. - /*!< Creates a table_configurator that loads and stores entries into - the internal collection of tables. It will use the "behavior" regarding - missing entries when load() is invoked. */ - - table_configurator(const table_configurator &to_copy); - - virtual ~table_configurator(); - - table_configurator &operator =(const table_configurator &to_copy); - - DEFINE_CLASS_NAME("table_configurator"); - - virtual void sections(structures::string_array &list); - //!< retrieves the section names into "list". - - void reset(); // clears out all contents. - - virtual bool get(const basis::astring §ion, const basis::astring &entry, - basis::astring &found); - //!< implements the configurator retrieval function. - - virtual bool put(const basis::astring §ion, const basis::astring &entry, - const basis::astring &to_store); - //!< implements the configurator storage function. - - virtual bool section_exists(const basis::astring §ion); - //!< true if the "section" is presently in the table config. - - virtual bool delete_section(const basis::astring §ion); - //!< removes the entire "section" specified. - - virtual bool delete_entry(const basis::astring §ion, const basis::astring &entry); - //!< removes the entry specified by the "section" and "entry" name. - - virtual bool get_section(const basis::astring §ion, structures::string_table &info); - //!< reads the entire table held under "section" into a table called "info". - - virtual bool put_section(const basis::astring §ion, const structures::string_table &info); - //!< writes a table called "info" into the "section" held here. - -private: - table_o_string_tables *_real_table; - //!< the data structure we're actually operating on. -}; - -} //namespace. - -#endif - diff --git a/core/library/configuration/variable_tokenizer.cpp b/core/library/configuration/variable_tokenizer.cpp deleted file mode 100644 index 7a1d0fd5..00000000 --- a/core/library/configuration/variable_tokenizer.cpp +++ /dev/null @@ -1,403 +0,0 @@ -// Name : variable_tokenizer -// Author : Chris Koeritz -/* -* Copyright (c) 1997-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "variable_tokenizer.h" - -#include -#include -#include -#include -#include -#include - -//#define DEBUG_VARIABLE_TOKENIZER - // uncomment for noisier run. - -const char *SPECIAL_VALUE = " "; - // special value stored for entries with assignment operators but no - // value contents. - -#undef LOG -#ifdef DEBUG_VARIABLE_TOKENIZER - #include - #define LOG(to_print) printf("%s\n", astring(to_print).s()); -#else - #define LOG(to_print) -#endif - -using namespace basis; -using namespace structures; -using namespace textual; - -namespace configuration { - -variable_tokenizer::variable_tokenizer(int max_bits) -: _implementation(new string_table(max_bits)), - _assignments(new astring("=")), - _separators(new astring(",")), - _quotes(new astring), - _nesting(false), - _comments(new astring), - _comment_number(1), - _add_spaces(false) -{} - -variable_tokenizer::variable_tokenizer(const astring &separator, const astring &assignment, - int max_bits) -: _implementation(new string_table(max_bits)), - _assignments(new astring(assignment)), - _separators(new astring(separator)), - _quotes(new astring), - _nesting(false), - _comments(new astring), - _comment_number(1), - _add_spaces(false) -{} - -variable_tokenizer::variable_tokenizer(const astring &separator, const astring &assignment, - const astring "es, bool nesting, int max_bits) -: _implementation(new string_table(max_bits)), - _assignments(new astring(assignment)), - _separators(new astring(separator)), - _quotes(new astring(quotes)), - _nesting(nesting), - _comments(new astring), - _comment_number(1), - _add_spaces(false) -{} - -variable_tokenizer::variable_tokenizer(const variable_tokenizer &to_copy) -: _implementation(new string_table), - _assignments(new astring), - _separators(new astring), - _quotes(new astring), - _nesting(false), - _comments(new astring), - _comment_number(1), - _add_spaces(false) -{ *this = to_copy; } - -variable_tokenizer::~variable_tokenizer() -{ - WHACK(_separators); - WHACK(_assignments); - WHACK(_implementation); - WHACK(_quotes); - WHACK(_comments); -} - -int variable_tokenizer::symbols() const { return _implementation->symbols(); } - -void variable_tokenizer::set_comment_chars(const astring &comments) -{ *_comments = comments; } - -const astring &variable_tokenizer::assignments() const { return *_assignments; } - -const astring &variable_tokenizer::separators() const { return *_separators; } - -const astring &variable_tokenizer::quotes() const { return *_quotes; } - -bool variable_tokenizer::exists(const astring &name) const -{ return !!_implementation->find(name); } - -void variable_tokenizer::reset() { _implementation->reset(); } - -const string_table &variable_tokenizer::table() const { return *_implementation; } - -string_table &variable_tokenizer::table() { return *_implementation; } - -variable_tokenizer &variable_tokenizer::operator =(const variable_tokenizer &to_copy) -{ - if (this == &to_copy) return *this; - *_implementation = *to_copy._implementation; - *_separators = *to_copy._separators; - *_assignments = *to_copy._assignments; - *_quotes = *to_copy._quotes; - _nesting = to_copy._nesting; - _add_spaces = to_copy._add_spaces; - return *this; -} - -astring variable_tokenizer::find(const astring &name) const -{ - astring *found = _implementation->find(name); - if (!found) return ""; - - // check that the contents are not just our significator of emptiness. - if (found->equal_to(SPECIAL_VALUE)) return ""; - return *found; -} - -bool variable_tokenizer::okay_for_variable_name(char to_check) const -{ - if (!to_check || separator(to_check) || assignment(to_check)) return false; - return true; -} - -bool variable_tokenizer::separator(char to_check) const -{ - // special case allows a CR separator to be either flavor. - if (parser_bits::is_eol(to_check) - && (astring::matches(*_separators, '\n') - || astring::matches(*_separators, '\r')) ) return true; - return astring::matches(*_separators, to_check); -} - -bool variable_tokenizer::assignment(char to_check) const -{ return astring::matches(*_assignments, to_check); } - -bool variable_tokenizer::quote_mark(char to_check) const -{ return astring::matches(*_quotes, to_check); } - -bool variable_tokenizer::comment_char(char to_check) const -{ return astring::matches(*_comments, to_check); } - -#define COOL to_tokenize.length() - // true if the string should continue to be parsed. - -// sets "current" to the first character in the string. -#define CHOP { \ - current = to_tokenize[0]; \ - to_tokenize.zap(0, 0); \ -} - -bool variable_tokenizer::parse(const astring &to_tokenize_in) -{ - FUNCDEF("parse"); - astring to_tokenize(to_tokenize_in); // de-const. -//hmmm: do we need a copy? try scooting based on a current pos. - - astring name, value; // accumulated during the loop. - char current; // the most recent character from to_tokenize. - bool just_ate_blank_line = false; - // records when we handle a blank line as a comment. - - // loop over the string. - while (COOL) { - name.reset(); - value.reset(); - - // pre-processing to remove extra eols and white space in front. - if (is_eol_a_separator() && parser_bits::is_eol(to_tokenize[0])) { - CHOP; - // chop any white space but don't eat any non-white space coming up. - while (COOL && parser_bits::white_space(current)) { - CHOP; - if (!parser_bits::white_space(current)) { - // oops; we ate something we shouldn't have, since it will be - // chopped when we get in the main loop. - to_tokenize.insert(0, astring(current, 1)); - } - } - } - - // chop the first character off for analysis. - CHOP; - - // ignore any white space until we hit a variable or other good stuff. - if (parser_bits::white_space_no_cr(current)) - continue; - - // ignore eol unless they are in separator list. - bool handle_as_comment = false; - if (parser_bits::is_eol(current) && !is_eol_a_separator()) { - continue; - } else if (just_ate_blank_line && parser_bits::is_eol(current)) { - just_ate_blank_line = false; - continue; - } else if (parser_bits::is_eol(current) && is_eol_a_separator()) { -//LOG("found eol and it's a separator here"); - handle_as_comment = true; - } - - if (comment_char(current) || handle_as_comment) { - // set our flag since we are going to eat the end of line in any case. - just_ate_blank_line = true; - // seek all text until next separator. - while (COOL && !separator(current)) { - value += current; - CHOP; - } - // add the item with our ongoing comment number. - a_sprintf name("%s%d", STRTAB_COMMENT_PREFIX, _comment_number); - _implementation->add(name, value); - _comment_number++; // go to next comment number to keep unique. -LOG(astring("got comment: ") + name + " -> " + value); - continue; // got our chunk, keep going. - } - - just_ate_blank_line = false; // reset our flag. - - // skip characters we can't use for a variable name. - if (!okay_for_variable_name(current)) continue; - - // we've found the start of a variable. - while (COOL && okay_for_variable_name(current)) { - // accumulate the variable name. - name += current; - CHOP; // get the next character. - } - if (!COOL) { - // we're at the end of the line, so deal with this situation. - if (!separator(current) && !parser_bits::white_space(current) ) - name += current; // get the character from the end of the line. -LOG(astring("last add: ") + name + " -> " + value); - _implementation->add(name, value); // store what we built. - continue; // skip the rest; we're at the END of the line man. - } - - // skip spaces after variable name. - while (COOL && parser_bits::white_space_no_cr(current)) CHOP; - - bool found_assignment = false; // assume there isn't one. - if (assignment(current)) { - // we found the assignment operator and are starting on the value. - CHOP; // skip the assignment operator. - found_assignment = true; - } - - // skip spaces after the assignment statement. - while (COOL && parser_bits::white_space_no_cr(current)) CHOP; - - // track the quoting that we have to deal with in parsing a value. - stack q_stack(!int(_nesting)); - // create an unbounded stack for nesting. - - while (COOL) { - // check if the current character is a quote. - bool ignore_separator = false; - if (quote_mark(current)) { - if (!q_stack.size()) { - // nothing on the stack yet, so start accumulating. - ignore_separator = true; - q_stack.push(current); - } else if (current == q_stack.top()) { - // we got the end of this quoting. - q_stack.pop(); - // check if we're done with any quotes. if not, we still need to - // ignore the separators. - if (q_stack.size()) - ignore_separator = true; - } else { - // if we are using a bounded stack, it means we only support one - // level of quoting at a time. thus, this quote character simply - // falls in as a regular character. but if we're unbound, then - // we can nest arbitrary levels of quotes. - if (q_stack.kind() == stack::UNBOUNDED) - q_stack.push(current); - // we have something on the stack already so we're still ignoring - // separators. we just don't care about this type of quote. - ignore_separator = true; - } - } else if (q_stack.size()) { - // it's not a quote but we're still trying to chow the matching - // quote character. - ignore_separator = true; - } - - // look for the separator. - if (!ignore_separator && separator(current)) { - break; - } - - // accumulate the value. - value += current; - CHOP; // get the next character. - } - // get the last character if it's relevant. - if (!separator(current) && !parser_bits::white_space(current) ) { - value += current; - } - - if (found_assignment && !value) { - // use our special case for empty values, since there was an assignment - // operator but no value afterwards. - value = SPECIAL_VALUE; - } - - // store the accumulated variable name and value, but only if the name - // is non-empty. otherwise, it's not much of a definition. - if (name.t()) { - // strip spaces at the end of the name. - while (parser_bits::white_space_no_cr(name[name.end()])) - name.zap(name.end(), name.end()); - // strip spaces at the end of the value unless it's the special case. - if (!value.equal_to(SPECIAL_VALUE)) { - while (parser_bits::white_space(value[value.end()])) - value.zap(value.end(), value.end()); - } -LOG(astring("normal add: ") + name + " -> " + value); - _implementation->add(name, value); // store what we built. - just_ate_blank_line = true; // flag that we don't want next EOL. - // reset, just in case. - name.reset(); - value.reset(); - } - } - // currently we just kind of bully through whatever string is provided and do not - // flag any error conditions. but people do like to know if it worked or not. they can - // make their own conclusions if there are not enough variables defined for their needs. - return true; -} - -bool variable_tokenizer::is_eol_a_separator() const -{ - for (int i = 0; i < _separators->length(); i++) { - char sep = _separators->get(i); - // correct the separator for platform when it's the end of the line. - if (parser_bits::is_eol(sep)) return true; - } - return false; -} - -void variable_tokenizer::text_form(astring &accumulator) const -{ - accumulator.reset(); - bool added_sep = false; - for (int i = 0; i < _implementation->symbols(); i++) { - added_sep = false; - if (!string_table::is_comment(_implementation->name(i))) { - // a normal assignment is here. - accumulator += _implementation->name(i); - if (_implementation->operator [](i).t()) { - if (_add_spaces) accumulator += " "; - accumulator += _assignments->get(0); - if (_add_spaces) accumulator += " "; - accumulator += _implementation->operator [](i); - } - } else { - // this one is a comment. just spit out the value. - if (_implementation->operator [](i).t()) - accumulator += _implementation->operator [](i); - } - // correct the separator for platform when it's the end of the line. - if (is_eol_a_separator()) { - accumulator += parser_bits::platform_eol_to_chars(); - } else { - added_sep = true; // record that we put a separator in there. - accumulator += _separators->get(0); - accumulator += ' '; - } - } - // strip the final separator and space back off, if we added them. - if (added_sep) - accumulator.zap(accumulator.end() - 1, accumulator.end()); -} - -astring variable_tokenizer::text_form() const -{ - astring accumulator; - text_form(accumulator); - return accumulator; -} - -} //namespace. - diff --git a/core/library/configuration/variable_tokenizer.h b/core/library/configuration/variable_tokenizer.h deleted file mode 100644 index 82b08357..00000000 --- a/core/library/configuration/variable_tokenizer.h +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef TOKENIZER_CLASS -#define TOKENIZER_CLASS - -/* -* Name : variable_tokenizer -* Author : Chris Koeritz -** -* Copyright (c) 1997-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include - -namespace configuration { - -//! Manages a bank of textual definitions of variables. -/*! - Manipulates strings containing variable definitions where a variable - is syntactically defined as a name, an assignment operator, and a value. - The string can optionally define many variables by placing a separator - character between the definitions. The assignment and separator are - referred to as sentinels in the following docs. - This class also supports quoted values if the appropriate constructor - is used. -*/ - -class variable_tokenizer : public virtual basis::root_object -{ -public: - enum constraints { DEFAULT_MAX_BITS = 7 }; - - variable_tokenizer(int max_bits = DEFAULT_MAX_BITS); - //!< creates a variable_tokenizer with the default characters. - /*!< this will not look for quote characters. the "max_bits" establishes - the hashing width for the internal table of strings; there will be - 2 ^ "max_bits" of space in the table. the default assignment operator - is '=' and the default separator is ','. */ - - variable_tokenizer(const basis::astring &separator, const basis::astring &assignment, - int max_bits = DEFAULT_MAX_BITS); - //!< creates an empty list of tokens and uses the specified sentinel chars. - /*!< the character that is expected to be between name/value pairs is - "separator". the "assignment" character is expected to be between each - name and its value. note that if the "separator" or "assignment" are more - than one character long, these will be taken as a set of valid characters - that can be used for those purposes. */ - - variable_tokenizer(const basis::astring &separator, const basis::astring &assignment, - const basis::astring "es, bool nesting = true, - int max_bits = DEFAULT_MAX_BITS); - //!< similar to the constructor above, but supports quoting. - /*!< if the "quotes" list is not empty, then those characters will be - treated as quoting characters that must be matched in pairs. inside a - quote, separators are ignored. if "nesting" is not true, then only one - level of quotes will be considered; the occurrence of other types of - quotes will be ignored until the original type is completed. */ - - variable_tokenizer(const variable_tokenizer &to_copy); - //!< builds a variable_tokenizer that is identical to "to_copy". - - virtual ~variable_tokenizer(); - - DEFINE_CLASS_NAME("variable_tokenizer"); - - void set_comment_chars(const basis::astring &comments); - //!< establishes a set of characters in "comments" as the comment items. - /*!< comments will be specially handled by being added to the string table - with the comment prefix. this allows them to be regenerated uniquely - later. */ - - variable_tokenizer &operator =(const variable_tokenizer &to_copy); - //!< makes this variable_tokenizer identical to "to_copy". - - int symbols() const; - //!< returns the number of entries in the variable_tokenizer. - - void reset(); - //!< clears all of the entries out. - - const structures::string_table &table() const; - //!< provides a constant peek at the string_table holding the values. - structures::string_table &table(); - //!< provides direct access to the string_table holding the values. - -//fix these docs. - bool parse(const basis::astring &to_tokenize); - //!< parses the string using our established sentinel characters. - /*!< attempts to snag as many value/pairs from "to_tokenize" as are - possible by using the current separator and assignment characters. - E.G.: if the separator is ';' and the assignment character - is '=', then one's string would look something like: @code - TEMP=c:\tmp; GLOB=c:\glob.exe; .... @endcode - whitespace is ignored if it's found (1) after a separator and before - the next variable name, (2) after the variable name and before the - assignment character, (3) after the assignment character and before the - value. this unfortunately implies that white space cannot begin or end - a value. - NOTE: unpredictable results will occur: if one's variables are - improperly formed, if assignment operators are missing or misplaced, - or if the separator character is used within the value. - NOTE: carriage returns are considered white-space and can exist in the - string as described above. - NOTE: parse is additive; if multiple calls to parse() occur, then the - symbol_table will be built from the most recent values found in the - parameters to parse(). if this is not desired, the symbol table's - reset() function can be used to empty out all variables. */ - - basis::astring find(const basis::astring &name) const; - //!< locates the value for a variable named "name" if it exists. - /*!< if "name" doesn't exist, then it returns an empty string. note that - an empty string might also indicate that the value is blank; locate is the - way to tell if a field is really missing. also note that when a variable - name is followed by an assignment operator and an empty value (e.g., - "avversione=" has no value), then a value of a single space character - will be stored. this ensures that the same format is used on the - output side, but it also means that if you access the table directly, - then you will get a space as the value. however, this function returns - an empty string for those entries to keep consistent with expectations. */ - - bool exists(const basis::astring &name) const; - //!< returns true if the "name" exists in the variable_tokenizer. - - basis::astring text_form() const; - //!< creates a new token list as a string of text. - /*!< the first separator and assignment characters in each set are used - to generate it. note that the whitespace that existed in the original - parsed string might not be exactly the same in the generated string. */ - void text_form(basis::astring &to_fill) const; - //!< like text_form() above, but stores into "to_fill". - - // dictates whether the output will have spaces between the assignment - // character and the key name and value. default is to not add them. - bool add_spaces() const { return _add_spaces; } - void add_spaces(bool add_them) { _add_spaces = add_them; } - - bool okay_for_variable_name(char to_check) const; - //!< true if "to_check" is a valid variable name character. - /*!< this includes any characters besides separators and assignments. */ - - const basis::astring &assignments() const; - //!< provides a peek at the assignments list. - const basis::astring &separators() const; - //!< provides a peek at the separators list. - const basis::astring "es() const; - //!< provides a peek at the quotes list. - - bool assignment(char to_check) const; - //!< true if "to_check" is a valid assignment operator. - - bool separator(char to_check) const; - //!< true if "to_check" is a valid separator. - - bool comment_char(char to_check) const; - //!< true if "to_check" is a registered comment character. - - bool is_eol_a_separator() const; - //!< reports whether any of the separators are an EOL character. - - bool quote_mark(char to_check) const; - //!< true if "to_check" is a member of the quotes list. - -private: - structures::string_table *_implementation; //!< holds the parsed values. - basis::astring *_assignments; //!< separates name from value. - basis::astring *_separators; //!< separates name/value pairs from other pairs. - basis::astring *_quotes; //!< the characters that are used for quoting. - bool _nesting; //!< if true, we nest arbitrary levels of quotes. - basis::astring *_comments; //!< if non-empty, characters that begin comments. - int _comment_number; //!< automatically incremented for use in comment tags. - bool _add_spaces; //!< records whether we add spaces around the assignment. -}; - -} //namespace. - -#endif - diff --git a/core/library/crypto/blowfish_crypto.cpp b/core/library/crypto/blowfish_crypto.cpp deleted file mode 100644 index 1e8ee443..00000000 --- a/core/library/crypto/blowfish_crypto.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/*****************************************************************************\ -* * -* Name : blowfish encryption * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "blowfish_crypto.h" -#include "ssl_init.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; - -namespace crypto { - -const int FUDGE = 128; - // extra space for the cipher's block size. blowfish is only 8 bytes for - // the cipher block size, but we ensure there will definitely be no - // problems. - -#undef set_key - // get rid of a macro we don't want. - -#undef LOG -#define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t) - -//#define DEBUG_BLOWFISH - // uncomment for noisier version. - -#ifdef DEBUG_BLOWFISH - // this macro checks on the validity of the key sizes (in bits). - #define DISCUSS_KEY_SIZE(key_size) \ - if (key_size < minimum_key_size()) { \ - continuable_error(static_class_name(), func, \ - a_sprintf("key size (%d bits) is less than minimum key size %d.", \ - key_size, minimum_key_size())); \ - } \ - if (key_size > maximum_key_size()) { \ - continuable_error(static_class_name(), func, \ - a_sprintf("key size (%d bits) is greater than maximum key size %d.", \ - key_size, maximum_key_size())); \ - } - - // this macro checks that the key in the byte array has enough bytes for - // the key size bits. - #define DISCUSS_PROVIDED_KEY(key_size, key) \ - if (key.length() * BITS_PER_BYTE < key_size) { \ - continuable_error(static_class_name(), func, \ - a_sprintf("key array length (%d) is less than required by key size " \ - "(%d bits).", key.length(), key_size)); \ - } -#else - #define DISCUSS_PROVIDED_KEY(key_size, key) - #define DISCUSS_KEY_SIZE(key_size) -#endif - -blowfish_crypto::blowfish_crypto(int key_size) -: _key_size(key_size), - _key(new byte_array) -{ -// FUNCDEF("constructor [int]"); - static_ssl_initializer(); - DISCUSS_KEY_SIZE(key_size); - if (key_size < minimum_key_size()) - _key_size = minimum_key_size(); - if (key_size > maximum_key_size()) - _key_size = maximum_key_size(); - generate_key(_key_size, *_key); -} - -blowfish_crypto::blowfish_crypto(const byte_array &key, int key_size) -: _key_size(key_size), - _key(new byte_array(key)) -{ -// FUNCDEF("constructor [byte_array/int]"); - // any problems with the key provided are horrid. they will yield a - // non-working blowfish object. - DISCUSS_KEY_SIZE(key_size); - DISCUSS_PROVIDED_KEY(key_size, key); - static_ssl_initializer(); -} - -blowfish_crypto::blowfish_crypto(const blowfish_crypto &to_copy) -: root_object(), - _key_size(to_copy._key_size), - _key(new byte_array(*to_copy._key)) -{ static_ssl_initializer(); } - -blowfish_crypto::~blowfish_crypto() -{ - WHACK(_key); -} - -int blowfish_crypto::key_size() const { return _key_size; } - -const byte_array &blowfish_crypto::get_key() const { return *_key; } - -int blowfish_crypto::minimum_key_size() { return 64; } - -int blowfish_crypto::maximum_key_size() { return 448; } - -blowfish_crypto &blowfish_crypto::operator = (const blowfish_crypto &to_copy) -{ - if (this == &to_copy) return *this; - _key_size = to_copy._key_size; - *_key = *to_copy._key; - return *this; -} - -bool blowfish_crypto::set_key(const byte_array &new_key, int key_size) -{ -// FUNCDEF("set_key"); - if (!new_key.length()) return false; - DISCUSS_KEY_SIZE(key_size); - DISCUSS_PROVIDED_KEY(key_size, new_key); - if ( (key_size < minimum_key_size()) || (key_size > maximum_key_size()) ) - return false; - if (new_key.length() * BITS_PER_BYTE < key_size) return false; - _key_size = key_size; - *_key = new_key; - return true; -} - -void blowfish_crypto::generate_key(int size, byte_array &new_key) -{ -// FUNCDEF("generate_key"); - DISCUSS_KEY_SIZE(size); - if (size < minimum_key_size()) - size = minimum_key_size(); - else if (size > maximum_key_size()) - size = maximum_key_size(); - int bytes = size / BITS_PER_BYTE; // calculate the number of bytes needed. - if (size % BITS_PER_BYTE) bytes++; // add one for non-integral portion. - new_key.reset(bytes); - for (int i = 0; i < bytes; i++) - new_key[i] = static_ssl_initializer().randomizer().inclusive(0, 255); -} - -SAFE_STATIC(mutex, __vector_init_lock, ) - -const byte_array &blowfish_crypto::init_vector() -{ - auto_synchronizer locking(__vector_init_lock()); - static byte_array to_return(EVP_MAX_IV_LENGTH); - static bool initted = false; - if (!initted) { - for (int i = 0; i < EVP_MAX_IV_LENGTH; i++) - to_return[i] = 214 - i; - initted = true; - } - return to_return; -} - -bool blowfish_crypto::encrypt(const byte_array &source, - byte_array &target) const -{ - FUNCDEF("encrypt"); - target.reset(); - if (!_key->length() || !source.length()) return false; - bool to_return = true; - - // initialize an encoding session. - EVP_CIPHER_CTX session; - EVP_CIPHER_CTX_init(&session); - EVP_EncryptInit_ex(&session, EVP_bf_cbc(), NIL, _key->observe(), - init_vector().observe()); - EVP_CIPHER_CTX_set_key_length(&session, _key_size); - - // allocate temporary space for encrypted data. - byte_array encoded(source.length() + FUDGE); - - // encrypt the entire source buffer. - int encoded_len = 0; - int enc_ret = EVP_EncryptUpdate(&session, encoded.access(), &encoded_len, - source.observe(), source.length()); - if (enc_ret != 1) { - continuable_error(class_name(), func, a_sprintf("encryption failed, " - "result=%d.", enc_ret)); - to_return = false; - } else { - // chop any extra space off. -// LOG(a_sprintf("encrypting bytes %d to %d.\n", i, edge)); - encoded.zap(encoded_len, encoded.last()); - target = encoded; - } - - // only add padding if we succeeded with the encryption. - if (enc_ret == 1) { - // finalize the encryption. - encoded.reset(FUDGE); // reinflate for padding. - int pad_len = 0; - enc_ret = EVP_EncryptFinal_ex(&session, encoded.access(), &pad_len); - if (enc_ret != 1) { - continuable_error(class_name(), func, a_sprintf("finalizing encryption " - "failed, result=%d.", enc_ret)); - to_return = false; - } else { -// LOG(a_sprintf("padding added %d bytes.\n", pad_len)); - encoded.zap(pad_len, encoded.last()); - target += encoded; - } - } - - EVP_CIPHER_CTX_cleanup(&session); - return to_return; -} - -bool blowfish_crypto::decrypt(const byte_array &source, - byte_array &target) const -{ - FUNCDEF("decrypt"); - target.reset(); - if (!_key->length() || !source.length()) return false; - bool to_return = true; - EVP_CIPHER_CTX session; - EVP_CIPHER_CTX_init(&session); -//LOG(a_sprintf("key size %d bits.\n", BITS_PER_BYTE * _key->length())); - EVP_DecryptInit_ex(&session, EVP_bf_cbc(), NIL, _key->observe(), - init_vector().observe()); - EVP_CIPHER_CTX_set_key_length(&session, _key_size); - - // allocate enough space for decoded bytes. - byte_array decoded(source.length() + FUDGE); - - int decoded_len = 0; - int dec_ret = EVP_DecryptUpdate(&session, decoded.access(), &decoded_len, - source.observe(), source.length()); - if (dec_ret != 1) { - continuable_error(class_name(), func, "decryption failed."); - to_return = false; - } else { -// LOG(a_sprintf(" decrypted size in bytes is %d.\n", decoded_len)); - decoded.zap(decoded_len, decoded.last()); - target = decoded; - } - - // only process padding if the first part of decryption succeeded. - if (dec_ret == 1) { - decoded.reset(FUDGE); // reinflate for padding. - int pad_len = 0; - dec_ret = EVP_DecryptFinal_ex(&session, decoded.access(), &pad_len); -// LOG(a_sprintf("padding added %d bytes.\n", pad_len)); - if (dec_ret != 1) { - continuable_error(class_name(), func, a_sprintf("finalizing decryption " - "failed, result=%d, padlen=%d, target had %d bytes.", dec_ret, - pad_len, target.length())); - to_return = false; - } else { - int dec_size = pad_len; - decoded.zap(dec_size, decoded.last()); - target += decoded; - } - } - - EVP_CIPHER_CTX_cleanup(&session); - return to_return; -} - -} //namespace. - - diff --git a/core/library/crypto/blowfish_crypto.h b/core/library/crypto/blowfish_crypto.h deleted file mode 100644 index 42a62503..00000000 --- a/core/library/crypto/blowfish_crypto.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef BLOWFISH_CRYPTO_CLASS -#define BLOWFISH_CRYPTO_CLASS - -/*****************************************************************************\ -* * -* Name : blowfish encryption * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace crypto { - -//! Provides BlowFish encryption on byte_arrays using the OpenSSL package. - -class blowfish_crypto : public virtual basis::root_object -{ -public: - blowfish_crypto(int key_size); - //!< this will create a new random key of the "key_size", in bits. - /*!< the valid sizes are from 64 bits to 448 bits (we are forcing a - higher minimum than the published algorithm because we have found smaller - keys to be unreliable during decryption. keys of 168 bits and larger - should be very secure. it is said that if a billion computers each tried - a billion keys a second, then a 168 bit key would take 10 * 10^24 years - to break (using brute force). this is essentially unbreakable since the - age of the universe is only 10 * 10^9 years so far. */ - - blowfish_crypto(const basis::byte_array &key, int key_size); - //!< uses a pre-existing "key". - - blowfish_crypto(const blowfish_crypto &to_copy); //!< copy constructor. - - virtual ~blowfish_crypto(); - - blowfish_crypto &operator = (const blowfish_crypto &to_copy); - - DEFINE_CLASS_NAME("blowfish_crypto"); - - int key_size() const; // returns the size of our key, in bits. - - static int minimum_key_size(); - //!< returns the minimum key size (in bits) supported here. - static int maximum_key_size(); - //!< returns the maximum key size (in bits) supported here. - - const basis::byte_array &get_key() const; //!< returns our current key. - - bool set_key(const basis::byte_array &new_key, int key_size); - //!< sets the encryption key to "new_key". - - static void generate_key(int size, basis::byte_array &new_key); - //!< creates a "new_key" of the "size" (in bits) specified. - - bool encrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< encrypts the "source" array into the "target" array. - - bool decrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< decrypts the "target" array from the encrypted "source" array. - - // seldom-needed methods... - - static const basis::byte_array &init_vector(); - //!< returns the initialization vector that is used by this class. - /*!< decryption of chunks that were encrypted by this class will require - the same init vector as this function returns. this is mainly provided - for third-party applications that want to be able to decrypt interoperably - with this class. if you are creating such an application but for some - reason cannot run this class in order to invoke this method, the vector - is created by the algorithm in this class's implementation file - (currently named blowfish_crypto.cpp). */ - -private: - int _key_size; //!< number of bits in the key. - basis::byte_array *_key; //!< our secret key. -}; - -} //namespace. - -#endif - diff --git a/core/library/crypto/makefile b/core/library/crypto/makefile deleted file mode 100644 index 97c7376a..00000000 --- a/core/library/crypto/makefile +++ /dev/null @@ -1,12 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -TYPE = library -PROJECT = crypto -SOURCE = blowfish_crypto.cpp rsa_crypto.cpp ssl_init.cpp -USE_SSL = t -TARGETS = crypto.lib - -include cpp/rules.def - diff --git a/core/library/crypto/rsa_crypto.cpp b/core/library/crypto/rsa_crypto.cpp deleted file mode 100644 index f50d3076..00000000 --- a/core/library/crypto/rsa_crypto.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/*****************************************************************************\ -* * -* Name : RSA public key encryption * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Supports public (and private) key encryption and decryption using the * -* OpenSSL package's support for RSA encryption. * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "rsa_crypto.h" -#include "ssl_init.h" - -#include -#include -#include -#include - -#include -#include - -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; - -namespace crypto { - -// notes from openssl docs: length to be encrypted in a chunk must be less than -// RSA_size(rsa) - 11 for the PKCS #1 v1.5 based padding modes, less than -// RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING and exactly RSA_size(rsa) -// for RSA_NO_PADDING. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -//nice printing method... RSA_print_fp(stdout, private_key, 0); - -rsa_crypto::rsa_crypto(int key_size) -: _key(NIL) -{ - _key = generate_key(key_size); // generate_key initializes ssl for us. -} - -rsa_crypto::rsa_crypto(const byte_array &key) -: _key(NIL) -{ - static_ssl_initializer(); - byte_array key_copy = key; - set_key(key_copy); -} - -rsa_crypto::rsa_crypto(rsa_st *key) -: _key(NIL) -{ - static_ssl_initializer(); - set_key(key); -} - -rsa_crypto::rsa_crypto(const rsa_crypto &to_copy) -: root_object(), - _key(NIL) -{ - static_ssl_initializer(); - set_key(to_copy._key); -} - -rsa_crypto::~rsa_crypto() -{ - RSA_free(_key); -} - -const rsa_crypto &rsa_crypto::operator = (const rsa_crypto &to_copy) -{ - if (this == &to_copy) return *this; - set_key(to_copy._key); - return *this; -} - -rsa_st *rsa_crypto::generate_key(int key_size) -{ - FUNCDEF("generate_key"); - if (key_size < 4) key_size = 4; // laughable lower default. - static_ssl_initializer(); - rsa_st *to_return = RSA_generate_key(key_size, 65537, NIL, NIL); - if (!to_return) { - continuable_error(static_class_name(), func, - a_sprintf("failed to generate a key of %d bits.", key_size)); - } - return to_return; -} - -bool rsa_crypto::check_key(rsa_st *key) { return RSA_check_key(key) == 1; } - -bool rsa_crypto::set_key(byte_array &key) -{ - FUNCDEF("set_key [byte_array]"); - if (!key.length()) return false; - if (_key) RSA_free(_key); - _key = RSA_new(); - abyte type; - if (!structures::detach(key, type)) return false; - if ( (type != 'r') && (type != 'u') ) return false; - // get the public key bits first. - byte_array n; - if (!structures::detach(key, n)) return false; - _key->n = BN_bin2bn(n.access(), n.length(), NIL); - if (!_key->n) return false; - byte_array e; - if (!structures::detach(key, e)) return false; - _key->e = BN_bin2bn(e.access(), e.length(), NIL); - if (!_key->e) return false; - if (type == 'u') return true; // done with public key. - - // the rest is for a private key. - byte_array d; - if (!structures::detach(key, d)) return false; - _key->d = BN_bin2bn(d.access(), d.length(), NIL); - if (!_key->d) return false; - byte_array p; - if (!structures::detach(key, p)) return false; - _key->p = BN_bin2bn(p.access(), p.length(), NIL); - if (!_key->p) return false; - byte_array q; - if (!structures::detach(key, q)) return false; - _key->q = BN_bin2bn(q.access(), q.length(), NIL); - if (!_key->q) return false; - byte_array dmp1; - if (!structures::detach(key, dmp1)) return false; - _key->dmp1 = BN_bin2bn(dmp1.access(), dmp1.length(), NIL); - if (!_key->dmp1) return false; - byte_array dmq1; - if (!structures::detach(key, dmq1)) return false; - _key->dmq1 = BN_bin2bn(dmq1.access(), dmq1.length(), NIL); - if (!_key->dmq1) return false; - byte_array iqmp; - if (!structures::detach(key, iqmp)) return false; - _key->iqmp = BN_bin2bn(iqmp.access(), iqmp.length(), NIL); - if (!_key->iqmp) return false; - int check = RSA_check_key(_key); - if (check != 1) { - continuable_error(static_class_name(), func, "failed to check the private " - "portion of the key!"); - return false; - } - - return true; -} - -bool rsa_crypto::set_key(rsa_st *key) -{ - FUNCDEF("set_key [rsa_st]"); - if (!key) return NIL; - // test the incoming key. - int check = RSA_check_key(key); - if (check != 1) return false; - // clean out the old key. - if (_key) RSA_free(_key); - _key = RSAPrivateKey_dup(key); - if (!_key) { - continuable_error(static_class_name(), func, "failed to create a " - "duplicate of the key!"); - return false; - } - return true; -} - -bool rsa_crypto::public_key(byte_array &pubkey) const -{ -// FUNCDEF("public_key"); - if (!_key) return false; - structures::attach(pubkey, abyte('u')); // signal a public key. - // convert the two public portions into binary. - byte_array n(BN_num_bytes(_key->n)); - int ret = BN_bn2bin(_key->n, n.access()); - byte_array e(BN_num_bytes(_key->e)); - ret = BN_bn2bin(_key->e, e.access()); - // pack those two chunks. - structures::attach(pubkey, n); - structures::attach(pubkey, e); - return true; -} - -bool rsa_crypto::private_key(byte_array &privkey) const -{ -// FUNCDEF("private_key"); - if (!_key) return false; - int posn = privkey.length(); - bool worked = public_key(privkey); // get the public pieces first. - if (!worked) return false; - privkey[posn] = abyte('r'); // switch public key flag to private. - // convert the multiple private portions into binary. - byte_array d(BN_num_bytes(_key->d)); - int ret = BN_bn2bin(_key->d, d.access()); - byte_array p(BN_num_bytes(_key->p)); - ret = BN_bn2bin(_key->p, p.access()); - byte_array q(BN_num_bytes(_key->q)); - ret = BN_bn2bin(_key->q, q.access()); - byte_array dmp1(BN_num_bytes(_key->dmp1)); - ret = BN_bn2bin(_key->dmp1, dmp1.access()); - byte_array dmq1(BN_num_bytes(_key->dmq1)); - ret = BN_bn2bin(_key->dmq1, dmq1.access()); - byte_array iqmp(BN_num_bytes(_key->iqmp)); - ret = BN_bn2bin(_key->iqmp, iqmp.access()); - // pack all those in now. - structures::attach(privkey, d); - structures::attach(privkey, p); - structures::attach(privkey, q); - structures::attach(privkey, dmp1); - structures::attach(privkey, dmq1); - structures::attach(privkey, iqmp); - return true; -} - -bool rsa_crypto::public_encrypt(const byte_array &source, - byte_array &target) const -{ -// FUNCDEF("public_encrypt"); - target.reset(); - if (!source.length()) return false; - const int max_chunk = RSA_size(_key) - 12; - - byte_array encoded(RSA_size(_key)); - for (int i = 0; i < source.length(); i += max_chunk) { - int edge = i + max_chunk - 1; - if (edge > source.last()) - edge = source.last(); - int next_chunk = edge - i + 1; - RSA_public_encrypt(next_chunk, &source[i], - encoded.access(), _key, RSA_PKCS1_PADDING); - target += encoded; - } - return true; -} - -bool rsa_crypto::private_decrypt(const byte_array &source, - byte_array &target) const -{ -// FUNCDEF("private_decrypt"); - target.reset(); - if (!source.length()) return false; - const int max_chunk = RSA_size(_key); - - byte_array decoded(max_chunk); - for (int i = 0; i < source.length(); i += max_chunk) { - int edge = i + max_chunk - 1; - if (edge > source.last()) - edge = source.last(); - int next_chunk = edge - i + 1; - int dec_size = RSA_private_decrypt(next_chunk, &source[i], - decoded.access(), _key, RSA_PKCS1_PADDING); - if (dec_size < 0) return false; // that didn't work. - decoded.zap(dec_size, decoded.last()); - target += decoded; - decoded.reset(max_chunk); - } - return true; -} - -bool rsa_crypto::private_encrypt(const byte_array &source, - byte_array &target) const -{ -// FUNCDEF("private_encrypt"); - target.reset(); - if (!source.length()) return false; - const int max_chunk = RSA_size(_key) - 12; - - byte_array encoded(RSA_size(_key)); - for (int i = 0; i < source.length(); i += max_chunk) { - int edge = i + max_chunk - 1; - if (edge > source.last()) - edge = source.last(); - int next_chunk = edge - i + 1; - RSA_private_encrypt(next_chunk, &source[i], - encoded.access(), _key, RSA_PKCS1_PADDING); - target += encoded; - } - return true; -} - -bool rsa_crypto::public_decrypt(const byte_array &source, - byte_array &target) const -{ -// FUNCDEF("public_decrypt"); - target.reset(); - if (!source.length()) return false; - const int max_chunk = RSA_size(_key); - - byte_array decoded(max_chunk); - for (int i = 0; i < source.length(); i += max_chunk) { - int edge = i + max_chunk - 1; - if (edge > source.last()) - edge = source.last(); - int next_chunk = edge - i + 1; - int dec_size = RSA_public_decrypt(next_chunk, &source[i], - decoded.access(), _key, RSA_PKCS1_PADDING); - if (dec_size < 0) return false; // that didn't work. - decoded.zap(dec_size, decoded.last()); - target += decoded; - decoded.reset(max_chunk); - } - return true; -} - -} //namespace. - diff --git a/core/library/crypto/rsa_crypto.h b/core/library/crypto/rsa_crypto.h deleted file mode 100644 index e43f1bdb..00000000 --- a/core/library/crypto/rsa_crypto.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef RSA_CRYPTO_CLASS -#define RSA_CRYPTO_CLASS - -/*****************************************************************************\ -* * -* Name : RSA public key encryption * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -// forward. -struct rsa_st; - -namespace crypto { - -//! Supports public key encryption and decryption. -/*! - This class uses the OpenSSL package's support for RSA encryption. -*/ - -class rsa_crypto : public virtual basis::nameable -{ -public: - rsa_crypto(int key_size); - //!< constructs using a randomized private key of the "key_size". - /*!< the "key_size" must be at least 1024 bits for acceptable security. - smaller keys are considered insecure. */ - - rsa_crypto(const basis::byte_array &key); - //!< constructs with the specified "key" as our private key. - /*!< the "key" is used for encryption rather than generating a random one. - the key is only valid if it was created with this class. also, if the key - is a public key, then only the public_encryption and public_decryption - methods will be available. */ - - rsa_crypto(rsa_st *key); - //!< starts with a pre-existing "key" in the low-level form. - - rsa_crypto(const rsa_crypto &to_copy); - - virtual ~rsa_crypto(); - - const rsa_crypto &operator = (const rsa_crypto &to_copy); - - DEFINE_CLASS_NAME("rsa_crypto"); - - bool set_key(basis::byte_array &key); - //!< resets this object's key to "key". - /*!< the key is only valid if this class created it. note: the "key" - is destructively consumed during the set method; do not pass in your - only copy. */ - - bool set_key(rsa_st *key); - //!< sets our new "key". - /*!< this must be a valid key created via the RSA algorithms. */ - - bool check_key(rsa_st *key); - //!< checks the RSA "key" provided for validity. - - bool public_encrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< encrypts "source" using our public key and stores it in "target". - /*!< public_encrypt and private_decrypt are a pair. an untrusted user can - encrypt with the public key and only the possessor of the private key - should be able to decrypt it. */ - bool private_decrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< decrypts "source" using our private key and stores it in "target". - - bool private_encrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< encrypts "source" using our private key and stores it in "target". - /*!< private_encrypt and public_decrypt are also a pair. the trusted - user with the private key can create encrypted chunks that anyone with - the public key can decrypt. */ - bool public_decrypt(const basis::byte_array &source, basis::byte_array &target) const; - //!< decrypts "source" using our public key and stores it in "target". - - bool public_key(basis::byte_array &pubkey) const; - //!< makes a copy of the public key held here. - bool private_key(basis::byte_array &privkey) const; - //!< makes a copy of the private key held here. - /*!< the private key should never be exposed to anyone else. */ - - static rsa_st *generate_key(int key_size); - //!< creates a random RSA key using the lower-level openssl methods. - -private: - rsa_st *_key; //!< our internal key. -}; - -} //namespace. - -#endif - diff --git a/core/library/crypto/ssl_init.cpp b/core/library/crypto/ssl_init.cpp deleted file mode 100644 index f32da3a4..00000000 --- a/core/library/crypto/ssl_init.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/*****************************************************************************\ -* * -* Name : SSL initialization helper * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ssl_init.h" - -#include -#include -#include - -#include -#include -#include - -using namespace basis; -using namespace mathematics; -using namespace structures; - -namespace crypto { - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -const int SEED_SIZE = 100; - // the size of the random seed that we'll use. - -// our global initialization object. -SAFE_STATIC_CONST(ssl_init, static_ssl_initializer, ) - -//#define DEBUG_SSL - // uncomment to cause more debugging information to be generated, plus - // more checking to be performed in the SSL support. - -ssl_init::ssl_init() -: c_rando() -{ -#ifdef DEBUG_SSL - CRYPTO_malloc_debug_init(); - CRYPTO_dbg_set_options(V_CRYPTO_MDEBUG_ALL); - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); -#endif - RAND_seed(random_bytes(SEED_SIZE).observe(), SEED_SIZE); -} - -ssl_init::~ssl_init() -{ - CRYPTO_cleanup_all_ex_data(); - ERR_remove_state(0); - CRYPTO_mem_leaks_fp(stderr); -} - -const chaos &ssl_init::randomizer() const { return c_rando; } - -byte_array ssl_init::random_bytes(int length) const -{ - byte_array seed; - for (int i = 0; i < length; i++) - seed += abyte(c_rando.inclusive(0, 255)); - return seed; -} - -} //namespace. - diff --git a/core/library/crypto/ssl_init.h b/core/library/crypto/ssl_init.h deleted file mode 100644 index 7357bd8c..00000000 --- a/core/library/crypto/ssl_init.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef SSL_INIT_CLASS -#define SSL_INIT_CLASS - -/*****************************************************************************\ -* * -* Name : SSL initialization helper * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace crypto { - -//! provides some initialization for the RSA and blowfish crypto. -/*! - This class does the SSL initialization needed before any functions can - be used. It also sets up the random seed for SSL. NOTE: you should never - need to use this class directly; just use the accessor function at the - very bottom and it will be managed globally for the entire program. -*/ - -class ssl_init : public virtual basis::nameable -{ -public: - ssl_init(); - ~ssl_init(); - - DEFINE_CLASS_NAME("ssl_init"); - - basis::byte_array random_bytes(int length) const; - //!< can be used to generate a random array of "length" bytes. - - const mathematics::chaos &randomizer() const; - //!< provides a random number generator for any encryption routines. - -private: - mathematics::chaos c_rando; //!< used for generating random numbers. -}; - -extern const ssl_init &static_ssl_initializer(); - //!< the main method for accessing the SSL initialization support. - -} //namespace. - -#endif - diff --git a/core/library/filesystem/byte_filer.cpp b/core/library/filesystem/byte_filer.cpp deleted file mode 100644 index 7aa05d78..00000000 --- a/core/library/filesystem/byte_filer.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/*****************************************************************************\ -* * -* Name : byte_filer * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_filer.h" - -#include -#include -#include -#include -#include - -#include -#include -#ifdef __UNIX__ - #include -#endif -#ifdef __WIN32__ - #include -#endif - -//#define DEBUG_BYTE_FILER - // uncomment for noisy version of class. - -using namespace basis; - -namespace filesystem { - -const size_t BTFL_FILE_TELL_LIMIT = size_t(2) * size_t(GIGABYTE); - // the largest a long integer can represent in the tell system call. - -class file_hider -{ -public: - FILE *fp; // the real file pointer. - - file_hider() : fp(NIL) {} -}; - -////////////// - -byte_filer::byte_filer() -: _handle(new file_hider), - _filename(new astring), - _auto_close(true) -{} - -byte_filer::byte_filer(const astring &filename, const astring &perms) -: _handle(new file_hider), - _filename(new astring), - _auto_close(true) -{ open(filename, perms); } - -byte_filer::byte_filer(const char *filename, const char *perms) -: _handle(new file_hider), - _filename(new astring), - _auto_close(true) -{ open(filename, perms); } - -byte_filer::byte_filer(bool auto_close, void *handle) -: _handle(new file_hider), - _filename(new astring), - _auto_close(auto_close) -{ - if (handle) { - _handle->fp = (FILE *)handle; - } -} - -byte_filer::~byte_filer() { close(); WHACK(_handle); WHACK(_filename); } - -astring byte_filer::filename() const { return *_filename; } - -size_t byte_filer::file_size_limit() { return BTFL_FILE_TELL_LIMIT; } - -bool byte_filer::open(const astring &filename, const astring &perms) -{ - close(); - _auto_close = true; // reset since we know we're opening this. - *_filename = filename; -#ifndef __WIN32__ - _handle->fp = filename.t()? fopen(filename.s(), perms.s()) : NIL; -#else - _handle->fp = filename.t()? _wfopen((wchar_t *)(UTF16 *)transcode_to_utf16(filename), - (wchar_t *)(UTF16 *)transcode_to_utf16(perms)) : NIL; - -#ifdef DEBUG_BYTE_FILER - if (!_handle->fp) - wprintf((wchar_t *)(UTF16 *)transcode_to_utf16("could not open: %ls\n"), - (wchar_t *)(UTF16 *)transcode_to_utf16(filename)); -#endif - -#endif - return good(); -} - -void byte_filer::close() -{ - *_filename = ""; - if (_auto_close && _handle->fp) fclose(_handle->fp); - _handle->fp = NIL; -} - -bool byte_filer::good() { return !!_handle->fp; } - -size_t byte_filer::tell() -{ - if (!_handle->fp) return 0; - long to_return = ::ftell(_handle->fp); - if (to_return == -1) { - // if we couldn't get the size, either the file isn't there or the size - // is too big for our OS to report. -///printf(a_sprintf("failed to tell size, calling it %.0f, and one plus that is %.0f\n", double(BTFL_FILE_TELL_LIMIT), double(long(long(BTFL_FILE_TELL_LIMIT) + 1))).s()); - if (good()) return BTFL_FILE_TELL_LIMIT; - else return 0; - } - return size_t(to_return); -} - -void *byte_filer::file_handle() { return _handle->fp; } - -bool byte_filer::eof() { return !_handle->fp ? true : !!feof(_handle->fp); } - -int byte_filer::read(abyte *buff, int size) -{ return !_handle->fp ? 0 : int(::fread((char *)buff, 1, size, _handle->fp)); } - -int byte_filer::write(const abyte *buff, int size) -{ return !_handle->fp ? 0 : int(::fwrite((char *)buff, 1, size, _handle->fp)); } - -int byte_filer::read(byte_array &buff, int desired_size) -{ - buff.reset(desired_size); - int to_return = read(buff.access(), desired_size); - buff.zap(to_return, buff.length() - 1); - return to_return; -} - -int byte_filer::write(const byte_array &buff) -{ return write(buff.observe(), buff.length()); } - -size_t byte_filer::length() -{ - size_t current_posn = tell(); - seek(0, FROM_END); // jump to end of file. - size_t file_size = tell(); // get position. - seek(int(current_posn), FROM_START); // jump back to previous place. - return file_size; -} - -int byte_filer::read(astring &s, int desired_size) -{ - s.pad(desired_size + 2); - int found = read((abyte *)s.observe(), desired_size); - if (non_negative(found)) s[found] = '\0'; - s.shrink(); - return found; -} - -int byte_filer::write(const astring &s, bool add_null) -{ - int len = s.length(); - if (add_null) len++; - return write((abyte *)s.observe(), len); -} - -void byte_filer::flush() -{ - if (!_handle->fp) return; - ::fflush(_handle->fp); -} - -bool byte_filer::truncate() -{ - flush(); - int fnum = fileno(_handle->fp); -#ifdef __WIN32__ - return SetEndOfFile((HANDLE)_get_osfhandle(fnum)); -#else - size_t posn = tell(); - // if we're at the highest point we can be, we no longer trust our - // ability to truncate properly. - if (posn >= file_size_limit()) - return false; - return !ftruncate(fnum, posn); -#endif -} - -bool byte_filer::seek(int where, origins origin) -{ - if (!_handle->fp) return false; - int real_origin; - switch (origin) { - case FROM_START: real_origin = SEEK_SET; break; - case FROM_END: real_origin = SEEK_END; break; - case FROM_CURRENT: real_origin = SEEK_CUR; break; - default: return false; // not a valid choice. - } - int ret = ::fseek(_handle->fp, where, real_origin); - return !ret; -} - -int byte_filer::getline(abyte *buff, int desired_size) -{ - if (!_handle->fp) return 0; - char *ret = ::fgets((char *)buff, desired_size, _handle->fp); - return !ret? 0 : int(strlen((char *)buff)) + 1; -} - -int byte_filer::getline(byte_array &buff, int desired_size) -{ - buff.reset(desired_size + 1); - return getline(buff.access(), desired_size); -} - -int byte_filer::getline(astring &buff, int desired_size) -{ - buff.pad(desired_size + 1); - int to_return = getline((abyte *)buff.access(), desired_size); - if (non_negative(to_return)) buff[to_return] = '\0'; - buff.shrink(); - return to_return; -} - -} //namespace. - - diff --git a/core/library/filesystem/byte_filer.h b/core/library/filesystem/byte_filer.h deleted file mode 100644 index 0e8bca78..00000000 --- a/core/library/filesystem/byte_filer.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef BYTE_FILER_CLASS -#define BYTE_FILER_CLASS - -/*****************************************************************************\ -* * -* Name : byte_filer * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace filesystem { - -// forward declarations. -class file_hider; - -//! Provides file managment services using the standard I/O support. - -class byte_filer -{ -public: - byte_filer(); - //!< constructs an object that doesn't access a file yet. - /*!< use open() to make the object valid. */ - - byte_filer(const basis::astring &filename, const basis::astring &permissions); - //!< opens a file "filename" as specified in "permissions". - /*!< these are identical to the standard I/O permissions: - - - "r" - opens text file for reading. - - "w" - opens text file for writing and discards any previous contents. - - "a" - opens text file for writing at end; appends to contents. - - "r+" - opens text file for update (both reading and writing). - - "w+" - creates a text file for update; any previous contents are lost. - - "a+" - opens or creates a text file for update, appending at end. - - a "b" can be added to the end of these to indicate a binary file should - be used instead of a text file. */ - - byte_filer(const char *filename, const char *permissions); - //!< synonym for above but takes char pointers. - - byte_filer(bool auto_close, void *opened); - //!< uses a previously "opened" stdio FILE handle. be careful! - /*!< the "opened" object must be a valid FILE pointer; void * is used to - avoid pulling in the stdio header. this method will not close the file - handle if "auto_close" is false. */ - - ~byte_filer(); - - static size_t file_size_limit(); - //!< returns the maximum size that seek and length can support. - /*!< use the huge_file class if you need to exceed the stdio limits. */ - - bool open(const basis::astring &filename, const basis::astring &permissions); - //!< opens a file with "filename" and "permissions" as in the constructor. - /*!< if a different file had already been opened, it is closed. */ - - void close(); - //!< shuts down the open file, if any. - /*!< open() will have to be invoked before this object can be used again. */ - - basis::astring filename() const; - //!< returns the filename that this was opened with. - - bool good(); - //!< returns true if the file seems to be in the appropriate desired state. - - size_t length(); - //!< returns the file's total length, in bytes. - /*!< this cannot accurately report a file length if it is file_size_limit() - or greater. */ - - size_t tell(); - //!< returns the current position within the file, in terms of bytes. - /*!< this is also limited to file_size_limit(). */ - - void flush(); - //!< forces any pending writes to actually be saved to the file. - - enum origins { - FROM_START, //!< offset is from the beginning of the file. - FROM_END, //!< offset is from the end of the file. - FROM_CURRENT //!< offset is from current cursor position. - }; - - bool seek(int where, origins origin = FROM_START); - //!< places the cursor in the file at "where", based on the "origin". - /*!< note that if the origin is FROM_END, then the offset "where" should - be a negative number if you're trying to access the interior of the file; - positive offsets indicate places after the actual end of the file. */ - - bool eof(); - //!< returns true if the cursor is at (or after) the end of the file. - - int read(basis::abyte *buffer, int buffer_size); - //!< reads "buffer_size" bytes from the file into "buffer". - /*!< for all of the read and write operations, the number of bytes that - is actually processed for the file is returned. */ - int write(const basis::abyte *buffer, int buffer_size); - //!< writes "buffer_size" bytes into the file from "buffer". - - int read(basis::byte_array &buffer, int desired_size); - //!< reads "buffer_size" bytes from the file into "buffer". - int write(const basis::byte_array &buffer); - //!< writes the "buffer" into the file. - - int read(basis::astring &buffer, int desired_size); - //!< read() pulls up to "desired_size" bytes from the file into "buffer". - /*!< since the read() will grab as much data as is available given that it - fits in "desired_size". null characters embedded in the file are a bad - issue here; some other method must be used to read the file instead (such - as the byte_array read above). the "buffer" is shrunk to fit the zero - terminator that we automatically add. */ - - int write(const basis::astring &buffer, bool add_null = false); - //!< stores the string in "buffer" into the file at the current position. - /*!< if "add_null" is true, then write() adds a zero terminator to what - is written into the file. otherwise just the string's non-null contents - are written. */ - - int getline(basis::abyte *buffer, int desired_size); - //!< reads a line of text (terminated by a return) into the "buffer". - int getline(basis::byte_array &buffer, int desired_size); - //!< reads a line of text (terminated by a return) into the "buffer". - int getline(basis::astring &buffer, int desired_size); - //!< reads a line of text (terminated by a return) into the "buffer". - - bool truncate(); - //!< truncates the file after the current position. - - void *file_handle(); - //!< provides a hook to get at the operating system's file handle. - /*!< this is of the type FILE *, as defined by . */ - -private: - file_hider *_handle; //!< the standard I/O support that we rely upon. - basis::astring *_filename; //!< holds onto our current filename. - bool _auto_close; //!< true if the object should close the file. - - // not to be called. - byte_filer(const byte_filer &); - byte_filer &operator =(const byte_filer &); -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/directory.cpp b/core/library/filesystem/directory.cpp deleted file mode 100644 index e4e3adcd..00000000 --- a/core/library/filesystem/directory.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/*****************************************************************************\ -* * -* Name : directory * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "directory.h" -#include "filename.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef __UNIX__ - #include - #include - #include - #include -#endif -#ifdef __WIN32__ - #include -#endif - -/* -#ifdef __WIN32__ - const int MAX_ABS_PATH = 2048; -#elif defined(__APPLE__) - const int MAX_ABS_PATH = 2048; -#else - const int MAX_ABS_PATH = MAX_ABS_PATH; -#endif -*/ - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -using namespace algorithms; -using namespace basis; -using namespace loggers; -using namespace structures; - -namespace filesystem { - -directory::directory(const astring &path, const char *pattern) -: _scanned_okay(false), - _path(new astring), - _files(new string_array), - _folders(new string_array), - _pattern(new astring(pattern)) -{ reset(path, pattern); } - -directory::directory(const directory &to_copy) -: _scanned_okay(false), - _path(new astring), - _files(new string_array), - _folders(new string_array), - _pattern(new astring) -{ reset(*to_copy._path, to_copy._pattern->observe()); } - -directory::~directory() -{ - _scanned_okay = false; - WHACK(_path); - WHACK(_files); - WHACK(_folders); - WHACK(_pattern); -} - -const astring &directory::path() const { return *_path; } - -const astring &directory::pattern() const { return *_pattern; } - -directory &directory::operator =(const directory &to_copy) -{ - if (this == &to_copy) return *this; // oops. - _scanned_okay = false; - reset(*to_copy._path, to_copy._pattern->observe()); - return *this; -} - -astring directory::absolute_path(const astring &rel_path) -{ - char abs_path[MAX_ABS_PATH + 1]; - abs_path[0] = '\0'; -#ifdef __WIN32__ - if (!_fullpath(abs_path, rel_path.s(), MAX_ABS_PATH)) return ""; - return abs_path; -#else - if (!realpath(rel_path.s(), abs_path)) return ""; - return abs_path; -#endif -} - -astring directory::current() -{ - astring to_return("."); // failure result. -#ifdef __WIN32__ - flexichar buffer[MAX_ABS_PATH + 1] = { '\0' }; - GetCurrentDirectory(MAX_ABS_PATH, buffer); - to_return = from_unicode_temp(buffer); -#else - char buffer[MAX_ABS_PATH + 1] = { '\0' }; - if (realpath(".", buffer)) to_return = buffer; -#endif - return to_return; -} - -bool directory::reset(const astring &path, const char *pattern) -{ *_path = path; *_pattern = pattern; return rescan(); } - -bool directory::move_up(const char *pattern) -{ - astring currdir = current(); - return reset(currdir + "/..", pattern); -} - -bool directory::move_down(const astring &subdir, const char *pattern) -{ - astring currdir = current(); - return reset(currdir + "/" + subdir, pattern); -} - -const string_array &directory::files() const { return *_files; } - -const string_array &directory::directories() const { return *_folders; } - -bool directory::rescan() -{ - FUNCDEF("rescan"); - _scanned_okay = false; - _files->reset(); - _folders->reset(); - astring cur_dir = "."; - astring par_dir = ".."; -#ifdef __WIN32__ - // start reading the directory. - WIN32_FIND_DATA wfd; - astring real_path_spec = *_path + "/" + *_pattern; - HANDLE search_handle = FindFirstFile(to_unicode_temp(real_path_spec), &wfd); - if (search_handle == INVALID_HANDLE_VALUE) return false; // bad path. - do { - // ignore the two standard directory entries. - astring filename_transcoded(from_unicode_temp(wfd.cFileName)); - if (!strcmp(filename_transcoded.s(), cur_dir.s())) continue; - if (!strcmp(filename_transcoded.s(), par_dir.s())) continue; - -#ifdef UNICODE -//temp - to_unicode_persist(fudgemart, filename_transcoded); - if (memcmp((wchar_t*)fudgemart, wfd.cFileName, wcslen(wfd.cFileName)*2)) - printf("failed to compare the string before and after transcoding\n"); -#endif - -//wprintf(to_unicode_temp("file is %ls\n"), (wchar_t*)to_unicode_temp(filename_transcoded)); - - filename temp_name(*_path, filename_transcoded.s()); - - // add this to the appropriate list. - if (temp_name.is_directory()) { - _folders->concatenate(filename_transcoded); - } else { - _files->concatenate(filename_transcoded); - -#ifdef UNICODE - to_unicode_persist(fudgemart2, temp_name.raw()); - FILE *fpjunk = _wfopen(fudgemart2, to_unicode_temp("rb")); - if (!fpjunk) - LOG(astring("failed to open the file for testing: ") + temp_name.raw() + "\n"); - if (fpjunk) fclose(fpjunk); -#endif - - } - } while (FindNextFile(search_handle, &wfd)); - FindClose(search_handle); -#endif -#ifdef __UNIX__ - DIR *dir = opendir(_path->s()); -//hmmm: could check errno to determine what caused the problem. - if (!dir) return false; - dirent *entry = readdir(dir); - while (entry) { - char *file = entry->d_name; - bool add_it = true; - if (!strcmp(file, cur_dir.s())) add_it = false; - if (!strcmp(file, par_dir.s())) add_it = false; - // make sure that the filename matches the pattern also. - if (add_it && !fnmatch(_pattern->s(), file, 0)) { - filename temp_name(*_path, file); - // add this to the appropriate list. - if (temp_name.is_directory()) - _folders->concatenate(file); - else - _files->concatenate(file); - } - entry = readdir(dir); - } - closedir(dir); -#endif - shell_sort(_files->access(), _files->length()); - shell_sort(_folders->access(), _folders->length()); - - _scanned_okay = true; - return true; -} - -bool directory::make_directory(const astring &path) -{ -#ifdef __UNIX__ - int mk_ret = mkdir(path.s(), 0777); -#endif -#ifdef __WIN32__ - int mk_ret = mkdir(path.s()); -#endif - return !mk_ret; -} - -bool directory::remove_directory(const astring &path) -{ -#ifdef __UNIX__ - int rm_ret = rmdir(path.s()); -#endif -#ifdef __WIN32__ - int rm_ret = rmdir(path.s()); -#endif - return !rm_ret; -} - -bool directory::recursive_create(const astring &directory_name) -{ -// FUNCDEF("recursive_create"); - filename dir(directory_name); - string_array pieces; - dir.separate(pieces); - for (int i = 0; i < pieces.length(); i++) { - // check each location along the way. - string_array partial = pieces.subarray(0, i); - filename curr; - curr.join(partial); // this is our current location. - // make sure, if we see a drive letter component, that we call it - // a proper directory name. - if (curr.raw()[curr.raw().end()] == ':') - curr = curr.raw() + "/"; - if (curr.exists()) { - if (curr.is_directory()) { - continue; // that's good. - } - return false; // if it's an existing file, we're hosed. - } - // the directory at this place doesn't exist yet. let's create it. - if (!directory::make_directory(curr.raw())) return false; - } - return true; -} - -} // namespace. diff --git a/core/library/filesystem/directory.h b/core/library/filesystem/directory.h deleted file mode 100644 index 7012d7be..00000000 --- a/core/library/filesystem/directory.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef DIRECTORY_CLASS -#define DIRECTORY_CLASS - -/*****************************************************************************\ -* * -* Name : directory * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace filesystem { - -//! Implements a scanner that finds all filenames in the directory specified. - -class directory : public virtual basis::root_object -{ -public: - directory(const basis::astring &path, const char *pattern = "*"); - //!< opens up the "path" specified and scans for files and subdirectories. - /*!< if the location was accessible, then the good() method returns true. - note that the "path" should just be a bare directory without any - wildcards attached. the "pattern" can be specified if you wish to - strain out just a subset of the files in the directory. it must meet - the same requirements that the operating system places on wildcard - patterns. */ - - directory(const directory &to_copy); - - virtual ~directory(); - - directory &operator =(const directory &to_copy); - - DEFINE_CLASS_NAME("directory"); - - bool good() const { return _scanned_okay; } - //!< true if the directory existed and its contents were readable. - - const basis::astring &path() const; - //!< returns the directory that we manage. - - const basis::astring &pattern() const; - //!< returns the pattern that the directory class scans for. - - static basis::astring absolute_path(const basis::astring &relative_path); - //!< returns the absolute path to a file with "relative_path". - /*!< an empty string is returned on failure. */ - - bool reset(const basis::astring &path, const char *pattern = "*"); - //!< gets rid of any current files and rescans the directory at "path". - /*!< a new "pattern" can be specified at this time also. */ - - bool move_up(const char *pattern = "*"); - //!< resets the directory to be its own parent. - - bool move_down(const basis::astring &subdir, const char *pattern = "*"); - //!< changes down into a "subdir" of this directory. - /*!< the "subdir" should be just the file name component to change into. - absolute paths will not work. for example, if a directory "/l/jed" has - a subdirectory named "clampett", then use: @code - my_dir->move_down("clampett") @endcode - */ - - bool rescan(); - //!< reads our current directory's contents over again. - - const structures::string_array &files() const; - //!< returns the list of files that we found in this directory. - /*!< these are all assumed to be located in our given path. to find out - more information about the files themselves, construct a filename object - with the path() and the file of interest. */ - - const structures::string_array &directories() const; - //!< these are the directory names from the folder. - /*!< they can also be examined using the filename object. note that - this does not include the entry for the current directory (.) or the - parent (..). */ - - // static methods of general directory-related interest. - - static basis::astring current(); - //!< returns the current directory, as reported by the operating system. - - static bool make_directory(const basis::astring &path); - //!< returns true if the directory "path" could be created. - - static bool remove_directory(const basis::astring &path); - //!< returns true if the directory "path" could be removed. - - static bool recursive_create(const basis::astring &directory_name); - //!< returns true if the "directory_name" can be created or already exists. - /*!< false returns indicate that the operating system wouldn't let us - make the directory, or that we didn't have sufficient permissions to - access an existing directory to view it or move into it. */ - -private: - bool _scanned_okay; //!< did this directory work out? - basis::astring *_path; //!< the directory we're looking at. - structures::string_array *_files; //!< the list of files. - structures::string_array *_folders; //!< the list of directories. - basis::astring *_pattern; //!< the pattern used to find the files. -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/directory_tree.cpp b/core/library/filesystem/directory_tree.cpp deleted file mode 100644 index 77ea9472..00000000 --- a/core/library/filesystem/directory_tree.cpp +++ /dev/null @@ -1,948 +0,0 @@ -/*****************************************************************************\ -* * -* Name : directory_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "directory.h" -#include "directory_tree.h" -#include "filename.h" -#include "filename_list.h" -#include "filename_tree.h" - -#include -#include -#include -#include -#include -#include - -#include - -using namespace basis; -using namespace structures; -using namespace nodes; -using namespace textual; - -//#define DEBUG_DIRECTORY_TREE - // uncomment for noisier version. - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) -//CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -////////////// - -namespace filesystem { - -class dir_tree_iterator : public filename_tree::iterator -{ -public: - filename_tree *_current; - - dir_tree_iterator(const filename_tree *initial, - tree::traversal_directions dir) - : filename_tree::iterator(initial, dir), _current(NIL) {} -}; - -////////////// - -directory_tree::directory_tree() -: _scanned_okay(false), - _path(new astring), - _pattern(new astring), - _real_tree(new filename_tree), - _ignore_files(false), - _creator(new fname_tree_creator) -{ -} - -directory_tree::directory_tree(const astring &path, const char *pattern, - bool ignore_files) -: _scanned_okay(false), - _path(new astring(path)), - _pattern(new astring(pattern)), - _real_tree(NIL), - _ignore_files(ignore_files), - _creator(new fname_tree_creator) -{ - reset(path, pattern); -} - -directory_tree::~directory_tree() -{ - _scanned_okay = false; - WHACK(_path); - WHACK(_pattern); - WHACK(_real_tree); - WHACK(_creator); -} - -const astring &directory_tree::path() const { return *_path; } - -int directory_tree::packed_size() const -{ - return 2 * PACKED_SIZE_INT32 - + _path->packed_size() - + _pattern->packed_size() - + _real_tree->recursive_packed_size(); -} - -void directory_tree::pack(byte_array &packed_form) const -{ - attach(packed_form, int(_scanned_okay)); - attach(packed_form, int(_ignore_files)); - _path->pack(packed_form); - _pattern->pack(packed_form); - _real_tree->recursive_pack(packed_form); -} - -bool directory_tree::unpack(byte_array &packed_form) -{ - int temp; - if (!detach(packed_form, temp)) return false; - _scanned_okay = temp; - if (!detach(packed_form, temp)) return false; - _ignore_files = temp; - if (!_path->unpack(packed_form)) return false; - if (!_pattern->unpack(packed_form)) return false; - WHACK(_real_tree); - _real_tree = (filename_tree *)packable_tree::recursive_unpack - (packed_form, *_creator); - if (!_real_tree) { - _real_tree = new filename_tree; // reset it. - return false; - } - return true; -} - -void directory_tree::text_form(astring &target, bool show_files) -{ - dir_tree_iterator *ted = start(directory_tree::prefix); - // create our iterator to do a prefix traversal. - - int depth; // current depth in tree. - filename curr; // the current path the iterator is at. - string_array files; // the filenames held at the iterator. - - while (current(*ted, curr, files)) { - // we have a good directory to show. - directory_tree::depth(*ted, depth); - target += string_manipulation::indentation(depth * 2) + astring("[") - + curr.raw() + "]" + parser_bits::platform_eol_to_chars(); - if (show_files) { - astring names; - for (int i = 0; i < files.length(); i++) names += files[i] + " "; - if (names.length()) { - astring split; - string_manipulation::split_lines(names, split, depth * 2 + 2); - target += split + parser_bits::platform_eol_to_chars(); - } - } - - // go to the next place. - next(*ted); - } - - throw_out(ted); -} - -void directory_tree::traverse(const astring &path, const char *pattern, - filename_tree &add_to) -{ -#ifdef DEBUG_DIRECTORY_TREE - FUNCDEF("traverse"); -#endif - // prepare the current node. - add_to._dirname = filename(path, astring::empty_string()); - add_to._files.reset(); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("working on node ") + add_to._dirname.raw()); -#endif - - // open the directory. - directory curr(path, "*"); - if (!curr.good()) return; - - if (!_ignore_files) { - // add all the files to the current node. - directory curr_stringent(path, pattern); - add_to._files = curr_stringent.files(); - } - - // now iterate across the directories here and add a sub-node for each one, - // and recursively traverse that sub-node also. - const string_array &dirs = curr.directories(); - for (int i = 0; i < dirs.length(); i++) { - filename_tree *new_branch = NIL; - astring new_path = path + filename::default_separator() + dirs[i]; -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("seeking path: ") + new_path); -#endif - for (int q = 0; q < add_to.branches(); q++) { - filename_tree *curr_kid = (filename_tree *)add_to.branch(q); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("curr kid: ") + curr_kid->_dirname); -#endif - if (filename(new_path).raw().iequals(filename - (curr_kid->_dirname).raw())) { - new_branch = curr_kid; -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("using existing branch for ") + new_path); -#endif - break; - } - } - if (!new_branch) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("adding new branch for ") + new_path); -#endif - new_branch = new filename_tree; - add_to.attach(new_branch); - new_branch->_depth = add_to._depth + 1; - } -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("traversing sub-node ") + new_path); -#endif - traverse(new_path, pattern, *new_branch); - } -} - -bool directory_tree::reset(const astring &path, const char *pattern) -{ - _scanned_okay = false; - WHACK(_real_tree); - *_path = path; - *_pattern = pattern; - _real_tree = new filename_tree; - - // check that the top-level is healthy. - directory curr(path, "*"); - if (!curr.good()) return false; - // our only exit condition; other directories might not be accessible - // underneath, but the top one must be accessible for us to even start - // the scanning. - - traverse(path, pattern, *_real_tree); - _scanned_okay = true;; - return true; -} - -dir_tree_iterator *directory_tree::start_at(filename_tree *start, - traversal_types type) const -{ - // translate to the lower level traversal enum. - tree::traversal_directions dir = tree::prefix; - if (type == infix) dir = tree::infix; - else if (type == postfix) dir = tree::postfix; - - return new dir_tree_iterator(start, dir); -} - -dir_tree_iterator *directory_tree::start(traversal_types type) const -{ - // translate to the lower level traversal enum. - tree::traversal_directions dir = tree::prefix; - if (type == infix) dir = tree::infix; - else if (type == postfix) dir = tree::postfix; - - return new dir_tree_iterator(_real_tree, dir); -} - -bool directory_tree::jump_to(dir_tree_iterator &scanning, - const astring &sub_path) -{ -#ifdef DEBUG_DIRECTORY_TREE - FUNCDEF("jump_to"); -#endif - string_array pieces; - filename(sub_path).separate(pieces); - for (int i = 0; i < pieces.length(); i++) { - filename_tree *curr = dynamic_cast(scanning.current()); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("at ") + curr->_dirname.raw()); -#endif - string_array sub_pieces = pieces.subarray(i, i); - filename curr_path; - curr_path.join(sub_pieces); - curr_path = filename(curr->_dirname.raw() + filename::default_separator() - + curr_path.raw()); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("made curr path ") + curr_path.raw()); -#endif - if (!curr) return false; - bool found_it = false; - for (int j = 0; j < curr->branches(); j++) { - filename_tree *sub = dynamic_cast(curr->branch(j)); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("looking at ") + sub->_dirname.raw()); -#endif - if (sub->_dirname.compare_prefix(curr_path)) { - // this part matches! - scanning.push(sub); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("found at ") + sub->_dirname.raw()); -#endif - found_it = true; - break; - } - } - if (!found_it) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("could not find ") + curr_path.raw()); -#endif - return false; - } - } - return true; -} - -filename_tree *directory_tree::goto_current(dir_tree_iterator &scanning) -{ - if (!scanning._current) { - // this one hasn't been advanced yet, or it's already over with. - scanning._current = (filename_tree *)scanning.next(); - } - // now check that we're healthy. - if (!scanning._current) return NIL; // all done. - - // cast the tree to the right type. - return dynamic_cast(scanning._current); -} - -bool directory_tree::current_dir(dir_tree_iterator &scanning, - filename &dir_name) -{ - dir_name = astring::empty_string(); - filename_tree *tof = goto_current(scanning); - if (!tof) return false; - dir_name = tof->_dirname; - return true; -} - -bool directory_tree::current(dir_tree_iterator &scanning, - filename &dir_name, string_array &to_fill) -{ - // clear any existing junk. - dir_name = astring::empty_string(); - to_fill.reset(); - - filename_tree *tof = goto_current(scanning); - if (!tof) return false; - - // fill in what they wanted. - dir_name = tof->_dirname; - tof->_files.fill(to_fill); - - return true; -} - -bool directory_tree::current(dir_tree_iterator &scanning, - filename &dir_name, filename_list &to_fill) -{ - // clear any existing junk. - dir_name = astring::empty_string(); - to_fill.reset(); - - filename_tree *tof = goto_current(scanning); - if (!tof) return false; - - // fill in what they wanted. - dir_name = tof->_dirname; - to_fill = tof->_files; - - return true; -} - -filename_list *directory_tree::access(dir_tree_iterator &scanning) -{ - filename_tree *tof = goto_current(scanning); - if (!tof) return NIL; - return &tof->_files; -} - -bool directory_tree::depth(dir_tree_iterator &scanning, int &depth) -{ - depth = -1; // invalid as default. - filename_tree *tof = goto_current(scanning); - if (!tof) return false; - depth = tof->_depth; - return true; -} - -bool directory_tree::children(dir_tree_iterator &scanning, int &kids) -{ - kids = -1; // invalid as default. - filename_tree *tof = goto_current(scanning); - if (!tof) return false; - kids = tof->branches(); - return true; -} - -bool directory_tree::next(dir_tree_iterator &scanning) -{ - scanning._current = (filename_tree *)scanning.next(); - return !!scanning._current; -} - -void directory_tree::throw_out(dir_tree_iterator * &to_whack) -{ - WHACK(to_whack); -} - -filename_tree *directory_tree::seek(const astring &dir_name_in, - bool ignore_initial) const -{ - FUNCDEF("seek"); - array examining; - // the list of nodes we're currently looking at. - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("seeking on root of: ") + *_path); -#endif - - astring dir_name = filename(dir_name_in).raw(); - // set the search path up to have the proper prefix. - if (ignore_initial) - dir_name = path() + filename::default_separator() - + filename(dir_name_in).raw(); - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("adding root: ") + _real_tree->_dirname); -#endif - examining += _real_tree; - - astring sequel; // holds extra pieces from filename comparisons. - - // chew on the list of nodes to examine until we run out. - while (examining.length()) { - int posn; - bool found = false; - // start looking at all the items in the list, even though we might have - // to abandon the iteration if we find a match. - filename_tree *check = NIL; - for (posn = 0; posn < examining.length(); posn++) { - check = examining[posn]; - filename current(check->_dirname); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("looking at ") + current.raw()); -#endif - if (current.compare_prefix(dir_name, sequel)) { - // we have a match! -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("matched! at ") + current.raw()); -#endif - found = true; - if (!sequel) { - // whoa! an exact match. we're done now. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("exact match at ") + current.raw() + "! done!!!"); -#endif - return check; - } else { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("inexact match because sequel=") + sequel); -#endif - } - break; - } - } - if (!found) return NIL; // we found nothing comparable. - - // we found a partial match. that means we should start looking at this - // node's children for the exact match. - if (!check) { - // this is a serious logical error! - LOG("serious logical error: tree was not located."); - return NIL; - } - examining.reset(); // clear the existing nodes. - for (int i = 0; i < check->branches(); i++) - examining += (filename_tree *)check->branch(i); - } - - return NIL; // we found nothing that looked like that node. -} - -bool directory_tree::calculate(bool just_size) -{ return calculate(_real_tree, just_size); } - -bool directory_tree::calculate(filename_tree *start, bool just_size) -{ - FUNCDEF("calculate"); - dir_tree_iterator *ted = start_at(start, directory_tree::postfix); - // create our iterator to do a postfix traversal. why postfix? well, - // prefix has been used elsewhere and since it doesn't really matter what - // order we visit the nodes here, it's good to change up. - - int depth; // current depth in tree. - filename curr; // the current path the iterator is at. - filename_list *files; // the filenames held at the iterator. - - while (directory_tree::current_dir(*ted, curr)) { - // we have a good directory to show. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("calcing node ") + curr.raw()); -#endif - files = directory_tree::access(*ted); - directory_tree::depth(*ted, depth); - for (int i = 0; i < files->elements(); i++) { - if (!files->borrow(i)->calculate(curr.raw(), just_size)) { - LOG(astring("failure to calculate ") + files->get(i)->text_form()); - } - } - - directory_tree::next(*ted); - } - - directory_tree::throw_out(ted); - return true; -} - -bool directory_tree::compare_trees(const directory_tree &source, - const directory_tree &target, filename_list &differences, - file_info::file_similarity how_to_compare) -{ - return compare_trees(source, astring::empty_string(), target, - astring::empty_string(), differences, how_to_compare); -} - -bool directory_tree::compare_trees(const directory_tree &source, - const astring &source_start_in, const directory_tree &target, - const astring &target_start_in, filename_list &differences, - file_info::file_similarity how_compare) -{ - FUNCDEF("compare_trees"); - differences.reset(); // prepare it for storage. - - // make sure we get canonical names to work with. - filename source_start(source_start_in); - filename target_start(target_start_in); - - dir_tree_iterator *ted = source.start(directory_tree::prefix); - // create our iterator to do a prefix traversal. - - astring real_source_start = source.path(); - if (source_start.raw().t()) { - // move to the right place. - real_source_start = real_source_start + filename::default_separator() - + source_start.raw(); - if (!directory_tree::jump_to(*ted, source_start.raw())) { - // can't even start comparing. - LOG(astring("failed to find source start in tree, given as ") - + source_start.raw()); - return false; - } - } - - filename curr; // the current path the iterator is at. - filename_list files; // the filenames held at the iterator. - - // calculate where our comparison point is on the source. - int source_pieces = 0; - { - string_array temp; - filename(real_source_start).separate(temp); - source_pieces = temp.length(); - } - - bool seen_zero_pieces = false; - while (directory_tree::current(*ted, curr, files)) { - // we're in a place in the source tree now. let's compare it with the - // target's recollection. - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("curr dir in tree: ") + curr.raw()); -#endif - - string_array pieces; - curr.separate(pieces); // get the components of the current location. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("name in pieces:") + pieces.text_form()); -#endif - pieces.zap(0, source_pieces - 1); - // snap the root components out of there. - - filename corresponding_name; - corresponding_name.join(pieces); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("computed target name as: ") + corresponding_name); -#endif - filename original_correspondence(corresponding_name); - - if (!corresponding_name.raw().t()) { - if (seen_zero_pieces) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("breaking out now due to empty correspondence")); -#endif - break; - } - seen_zero_pieces = true; - } - if (target_start.raw().t()) { - corresponding_name = filename(target_start.raw() - + filename::default_separator() + corresponding_name.raw()); - } -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("target with start is: ") + corresponding_name); -#endif - - filename_tree *target_now = target.seek(corresponding_name.raw(), true); - if (!target_now) { - // that entire sub-tree is missing. add all of the files here into - // the list. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("could not find dir in target for ") + curr.raw() - + " which we computed corresp as " + corresponding_name.raw()); -#endif - } - - // now scan across all the files that are in our source list. - for (int i = 0; i < files.elements(); i++) { - if (!target_now // there was no node, so we're adding everything... - || !target_now->_files.member_with_state(*files[i], how_compare) ) { - // ... or we need to add this file since it's missing. - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("adding record: ") + files[i]->text_form()); -#endif - - file_info *new_record = new file_info(*files[i]); - // record the file time for use later in saving. - new_record->calculate(curr, true); - astring original = new_record->raw(); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("current: ") + new_record->raw()); -#endif - - astring actual_name = source_start.raw(); -#ifdef DEBUG_DIRECTORY_TREE - if (actual_name.t()) LOG(astring("sname=") + actual_name); -#endif - if (actual_name.length()) actual_name += filename::default_separator(); - actual_name += original_correspondence.raw(); - if (actual_name.length()) actual_name += filename::default_separator(); - actual_name += new_record->raw(); -#ifdef DEBUG_DIRECTORY_TREE - if (actual_name.t()) LOG(astring("sname=") + actual_name); -#endif - (filename &)(*new_record) = filename(actual_name); - - astring targ_name = corresponding_name.raw(); -#ifdef DEBUG_DIRECTORY_TREE - if (targ_name.t()) LOG(astring("tname=") + targ_name); -#endif - if (targ_name.length()) targ_name += filename::default_separator(); - targ_name += original; -#ifdef DEBUG_DIRECTORY_TREE - if (targ_name.t()) LOG(astring("tname=") + targ_name); -#endif - - new_record->secondary(targ_name); - - differences += new_record; -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("came out as: ") + new_record->text_form()); -#endif - } - } - - // go to the next place. - directory_tree::next(*ted); - } - - directory_tree::throw_out(ted); - - return true; -} - -outcome directory_tree::find_common_root(const astring &finding, bool exists, - filename_tree * &found, astring &reassembled, string_array &pieces, - int &match_place) -{ -#ifdef DEBUG_DIRECTORY_TREE - FUNCDEF("find_common_root"); -#endif - // test the path to find what it is. - filename adding(finding); - if (exists && !adding.good()) - return common::BAD_INPUT; // not a good path. - int file_subtract = 0; // if it's a file, then we remove last component. - if (exists && !adding.is_directory()) file_subtract = 1; - - // break up the path into pieces. - pieces.reset(); - adding.separate(pieces); - - // break up our root into pieces; we must take off components that are - // already in the root. - string_array root_pieces; - filename temp_file(path()); - temp_file.separate(root_pieces); - - // locate the last place where the path we were given touches our tree. - // it could be totally new, partially new, or already contained. - filename_tree *last_match = _real_tree; // where the common root is located. - int list_length = pieces.length() - file_subtract; - reassembled = ""; - - // we must put all the pieces in that already come from the root. - for (int i = 0; i < root_pieces.length() - 1; i++) { - bool add_slash = false; - if (reassembled.length() && (reassembled[reassembled.end()] != '/') ) - add_slash = true; - if (add_slash) reassembled += "/"; - reassembled += pieces[i]; - if (reassembled[reassembled.end()] == ':') { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("skipping drive component ") + reassembled); -#endif - continue; - } - } - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("after pre-assembly, path is ") + reassembled); -#endif - - outcome to_return = common::NOT_FOUND; - - for (match_place = root_pieces.length() - 1; match_place < list_length; - match_place++) { - // add a slash if there's not one present already. - bool add_slash = false; - if (reassembled.length() && (reassembled[reassembled.end()] != '/') ) - add_slash = true; - // add the next component in to our path. - if (add_slash) reassembled += "/"; - reassembled += pieces[match_place]; - // special case for dos paths. - if (reassembled[reassembled.end()] == ':') { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("skipping drive component ") + reassembled); -#endif - continue; - } - reassembled = filename(reassembled).raw(); // force compliance with OS. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("now seeking ") + reassembled); -#endif - filename_tree *sought = seek(reassembled, false); - if (!sought) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("couldn't find ") + reassembled); -#endif - if (!exists && (match_place == list_length - 1)) { - // see if we can get a match on a file rather than a directory, but - // only if we're near the end of the compare. - if (last_match->_files.member(pieces[match_place])) { - // aha! a file match. - to_return = common::OKAY; - match_place--; - break; - } - } - match_place--; - break; - } else { - // record where we last had some success. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("found subtree for ") + reassembled); -#endif - last_match = sought; - } - } - // this is a success, but our loop structure can put us one past the right - // place. - if (match_place >= list_length) { - match_place = list_length - 1; - to_return = common::OKAY; - } - - found = last_match; - return to_return; -} - -outcome directory_tree::add_path(const astring &new_item, bool just_size) -{ - FUNCDEF("add_path"); - // test the path to find out what it is. - filename adding(new_item); - if (!adding.good()) { - LOG(astring("non-existent new item! ") + new_item); - return common::BAD_INPUT; // not a good path. - } - int file_subtract = 0; // if it's a file, then we remove last component. - if (!adding.is_directory()) file_subtract = 1; -#ifdef DEBUG_DIRECTORY_TREE - if (file_subtract) LOG(astring("adding a file ") + new_item) - else LOG(astring("adding a directory ") + new_item); -#endif - - // find the common root, break up the path into pieces, and tell us where - // we matched. - string_array pieces; - filename_tree *last_match = NIL; - int comp_index; - astring reassembled; // this will hold the common root. - outcome ret = find_common_root(new_item, true, last_match, reassembled, - pieces, comp_index); - if (!last_match) { - LOG(astring("serious error finding common root for ") + new_item - + ", got NIL tree."); - return common::FAILURE; // something serious isn't right. - } - - if (!file_subtract) { - if (ret != common::OKAY) { - // if it's a new directory, we add a new node for traverse to work on. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("now adding node for ") + reassembled); -#endif - filename_tree *new_branch = new filename_tree; - new_branch->_depth = last_match->_depth + 1; - last_match->attach(new_branch); - last_match = new_branch; - } else { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("matched properly. reassembled set to ") + reassembled); -#endif - } - } - - if (file_subtract) { - if (ret != common::OKAY) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("common gave us posn of: ") + reassembled); -#endif - // handle the case for files now that we have our proper node. - string_array partial_pieces; - filename(reassembled).separate(partial_pieces); - int levels_missing = pieces.length() - partial_pieces.length(); - - // we loop over all the pieces that were missing in between the last - // common root and the file's final location. - for (int i = 0; i < levels_missing; i++) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("adding intermediate directory: ") + reassembled); -#endif - filename_tree *new_branch = new filename_tree; - new_branch->_depth = last_match->_depth + 1; - new_branch->_dirname = filename(reassembled).raw(); - last_match->attach(new_branch); - last_match = new_branch; - reassembled += astring("/") + pieces[partial_pieces.length() + i]; - reassembled = filename(reassembled).raw(); // canonicalize. - } - } - - if (!last_match->_files.find(pieces[pieces.last()])) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("adding new file ") + pieces[pieces.last()] - + " at " + reassembled); -#endif - file_info *to_add = new file_info(pieces[pieces.last()], 0); - to_add->calculate(reassembled, just_size); - last_match->_files += to_add; - } else { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("not adding existing file ") + pieces[pieces.last()] - + " at " + reassembled); -#endif - } - } else { - // handle the case for directories. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("doing traverse in ") + last_match->_dirname - + " to add " + reassembled); -#endif - traverse(reassembled, "*", *last_match); -//hmmm: maybe provide pattern capability instead of assuming all files. - calculate(last_match, just_size); - } - - return common::OKAY; -} - -outcome directory_tree::remove_path(const astring &zap_item) -{ -#ifdef DEBUG_DIRECTORY_TREE - FUNCDEF("remove_path"); -#endif - // find the common root, if one exists. if not, we're not going to do this. - string_array pieces; - filename_tree *last_match = NIL; - int comp_index; - astring reassembled; - outcome ret = find_common_root(zap_item, false, last_match, reassembled, - pieces, comp_index); - if (!last_match) return common::NOT_FOUND; - // if we didn't actually finish iterating to the file, then we're not - // whacking anything. - if (ret != common::OKAY) { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("got error seeking ") + zap_item + " of " - + common::outcome_name(ret)); -#endif - return ret; - } - - if (comp_index == pieces.last()) { - // if the names match fully, then we're talking about a directory. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("found directory match for ") + zap_item); -#endif - } else { -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("may have found file match for ") + zap_item); -#endif - filename to_seek(pieces[pieces.last()]); - if (!last_match->_files.member(to_seek)) { - // this file is not a member, so we must say it's not found. -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("couldn't find file match in common root for ") + zap_item); -#endif - return common::NOT_FOUND; - } else { - int indy = last_match->_files.locate(to_seek); -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("found match to remove for ") + zap_item); -#endif - last_match->_files.zap(indy, indy); - return common::OKAY; // done! - } - } - -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("going to whack node at: ") + last_match->_dirname.raw()); -#endif - - // we're whacking directories, so we need to take out last_match and below. - filename_tree *parent = (filename_tree *)last_match->parent(); - if (!parent || (last_match == _real_tree)) { - // this seems to be matching the whole tree. we disallow that. -#ifdef DEBUG_DIRECTORY_TREE - LOG("there's a problem whacking this node; it's the root."); -#endif - return common::BAD_INPUT; - } -#ifdef DEBUG_DIRECTORY_TREE - LOG(astring("pruning tree at ") + last_match->_dirname.raw()); -#endif - parent->prune(last_match); - WHACK(last_match); - - return common::OKAY; -} - -} //namespace. - - diff --git a/core/library/filesystem/directory_tree.h b/core/library/filesystem/directory_tree.h deleted file mode 100644 index 9c89ba43..00000000 --- a/core/library/filesystem/directory_tree.h +++ /dev/null @@ -1,224 +0,0 @@ -#ifndef DIRECTORY_TREE_CLASS -#define DIRECTORY_TREE_CLASS - -/*****************************************************************************\ -* * -* Name : directory_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "directory.h" -#include "file_info.h" - -#include -#include -#include -#include - -namespace filesystem { - -// forward declarations. -class dir_tree_iterator; -class filename; -class filename_list; -class filename_tree; -class fname_tree_creator; - -//! An object that traverses directory trees and provides a view of all files. - -class directory_tree : public virtual basis::packable -{ -public: - directory_tree(); //!< constructs an empty tree. - - directory_tree(const basis::astring &path, const char *pattern = "*", - bool ignore_files = false); - //!< opens up the "path" specified and scans for files and subdirectories. - /*!< if the location was accessible, then the good() method returns true. - note that the "path" should just be a bare directory without any - wildcards attached. the "pattern" can be specified if you wish to - strain out just a subset of the files in the directory. note that - unlike the directory object, directory_tree applies the wildcard to - filenames only--all sub-directories are included. the pattern must meet - the same requirements that the operating system places on wildcard - patterns. if "ignore_files" is true, then no files are considered and - only the tree of directories is gathered. */ - - ~directory_tree(); - - DEFINE_CLASS_NAME("directory_tree"); - - bool good() const { return _scanned_okay; } - //!< returns true if the directory existed and we read its contents. - - const basis::astring &path() const; - //!< returns the root of the directory tree that we manage. - - bool reset(const basis::astring &path, const char *pattern = "*"); - //!< gets rid of any current files and rescans the directory at "path". - /*!< a new "pattern" can be specified at this time also. true is returned - if the process was started successfully at "path"; there might be - problems with subdirectories, but at least the "path" got validated. */ - - filename_tree *seek(const basis::astring &dir_name, bool ignore_initial) const; - //!< finds the "dir_name" in our tree. - /*!< locates the node that corresponds to the directory name contained in - "dir_name" and returns the filename_tree rooted at that node. if the - "ignore_initial" flag is true, then dir_name is expected to omit the - path() where "this" tree is rooted. */ - - virtual int packed_size() const; - //!< reports the size after packing up the tree. - virtual void pack(basis::byte_array &packed_form) const; - //!< packs the directory_tree into a byte_array. - virtual bool unpack(basis::byte_array &packed_form); - //!< unpacks the directory_tree from a byte_array. - - bool calculate(bool just_size); - //!< visits each file in the directory_tree and calculates its attributes. - /*!< the attributes include file size and checksum. if "just_size" is - true, then no checksum is computed. */ - - bool calculate(filename_tree *start, bool just_size); - //!< a calculate method that starts at a specific node rather than the root. - - basis::outcome add_path(const basis::astring &new_item, bool just_size = false); - //!< adds a "new_item" into the tree. - /*!< this is useful when one knows that new files exist under the - directory, but one doesn't want to recalculate the entire tree. the new - item will automatically be calculated. the item can be either a file or - directory that's under the root. the root directory name should not be - included in the "new_item". */ - - basis::outcome remove_path(const basis::astring &zap_item); - //!< removes the "zap_item" from the tree. - /*!< this only works for cases where one knows that an item has been - removed in the filesystem. if the item is still really there, then the - next rescan will put it back into the tree. */ - - static bool compare_trees(const directory_tree &source, - const directory_tree &target, filename_list &differences, - file_info::file_similarity how_to_compare); - //!< compares the tree in "source" with the tree in "target". - /*!< the two root names may be different, but everything below the root - in "source" will be checked against "target". the "differences" between - the two trees will be compiled. note that this does not perform any disk - access; it merely compares the two trees' current contents. - the "differences" list's members will have a primary filename set to - the source path and an alternate filename set to the location in the - target. the "how_to_compare" value will dictate what aspects of file - equality are used. */ - - static bool compare_trees(const directory_tree &source, - const basis::astring &source_start, const directory_tree &target, - const basis::astring &target_start, filename_list &differences, - file_info::file_similarity how_to_compare); - // compares the trees but not at their roots. the location on the source - // side is specified by "source_start", which must be a path found under - // the "source" tree. similarly, the "target_start" will be the location - // compared with the "source" + "source_start". the "diffs" will still - // be valid with respect to "source" rather than "source_start". - - void text_form(basis::astring &tree_dump, bool show_files = true); - //!< provides a visual representation of the tree in "tree_dump". - /*!< if "show_files" is not true, then only the directories will be - shown. */ - - // Note on the iterator functions: the iterator becomes invalid if the - // directory tree is reset. the only valid operation on the iterator - // at that point is to call throw_out(). - - enum traversal_types { - prefix, //!< prefix means that subnodes are processed after their parent. - infix, //!< infix (for binary trees) goes 1) left, 2) current, 3) right. - postfix //!< postfix means that subnodes are traversed first (depth first). - }; - - dir_tree_iterator *start(traversal_types type) const; - //!< starts an iterator on the directory tree. - - dir_tree_iterator *start_at(filename_tree *start, - traversal_types type) const; - //!< starts the iterator at a specific "start" node. - - static bool jump_to(dir_tree_iterator &scanning, const basis::astring &sub_path); - //!< seeks to a "sub_path" below the iterator's current position. - /*!< tries to take the iterator "scanning" down to a "sub_path" that is - underneath its current position. true is returned on success. */ - - static bool current_dir(dir_tree_iterator &scanning, filename &dir_name); - //!< sets "dir_name" to the directory name at the "scanning" location. - - static bool current(dir_tree_iterator &scanning, filename &dir_name, - structures::string_array &to_fill); - //!< retrieves the information for the iterator's current location. - /*!< fills the "to_fill" array with filenames that are found at the - "scanning" iterator's current position in the tree. the "dir_name" - for that location is also set. if the iterator has ended, then false - is returned. */ - - static bool current(dir_tree_iterator &scanning, filename &dir_name, - filename_list &to_fill); - //!< similar to the above but provides a list of the real underlying type. - - static filename_list *access(dir_tree_iterator &scanning); - //!< more dangerous operation that lets the actual list be manipulated. - /*!< NIL is returned if there was a problem accessing the tree - at the iterator position. */ - - static bool depth(dir_tree_iterator &scanning, int &depth); - //!< returns the current depth of the iterator. - /*!< a depth of zero means the iterator is at the root node for the tree. */ - - static bool children(dir_tree_iterator &scanning, int &children); - //!< returns the number of children for the current node. - - static bool next(dir_tree_iterator &scanning); - //!< goes to the next filename in the "scanning" iterator. - /*!< true is returned if there is an entry there. */ - - static void throw_out(dir_tree_iterator * &to_whack); - //!< cleans up an iterator that was previously opened with start(). - -private: - bool _scanned_okay; //!< did this directory work out? - basis::astring *_path; //!< the directory we're looking at. - basis::astring *_pattern; //!< the pattern used to find the files. - filename_tree *_real_tree; //!< the tree of directory contents we build. - bool _ignore_files; //!< true if they don't care about the files. - fname_tree_creator *_creator; //!< creates blank trees during unpacking. - - static filename_tree *goto_current(dir_tree_iterator &scanning); - //!< goes to the current node for "scanning" and returns the tree there. - /*!< if there are no nodes left, NIL is returned. */ - - void traverse(const basis::astring &path, const char *pattern, - filename_tree &add_to); - //!< recursively adds a "path" given the filename "pattern". - /*!< assuming that we want to add the files at "path" using the "pattern" - into the current node "add_to", we will also scoot down all sub-dirs - and recursively invoke traverse() to add those also. */ - - basis::outcome find_common_root(const basis::astring &path, bool exists, - filename_tree * &common_root, basis::astring &common_path, - structures::string_array &pieces, int &match_place); - //!< locates the node where this tree and "path" have membership in common. - /*!< if "exists" is true, then the "path" is tested for existence and - otherwise it's assumed that the path no longer exists. the "common_root" - is the last node that's in both places, the "common_path" is the name of - that location, the list of "pieces" is "path" broken into its components, - and the "match_place" is the index in "pieces" of the common node. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/file_info.cpp b/core/library/filesystem/file_info.cpp deleted file mode 100644 index ed66d7b4..00000000 --- a/core/library/filesystem/file_info.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/*****************************************************************************\ -* * -* Name : file_info * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "file_info.h" -#include "huge_file.h" - -#include -#include -#include -#include -#include -#include - -#include - -#define DEBUG_FILE_INFO - // uncomment for noisy version. - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -using namespace basis; -using namespace structures; - -namespace filesystem { - -file_info::file_info() -: filename(astring::empty_string()), - _file_size(0), - _time(), - _checksum(), - c_secondary(), - c_attachment() -{} - -file_info::file_info(const filename &to_copy, double file_size, - const file_time &time, int checksum) -: filename(to_copy), - _file_size(file_size), - _time(time), - _checksum(checksum), - c_secondary(), - c_attachment() -{} - -file_info::file_info(const file_info &to_copy) -: filename(to_copy), - _file_size(to_copy._file_size), - _time(to_copy._time), - _checksum(to_copy._checksum), - c_secondary(to_copy.c_secondary), - c_attachment(to_copy.c_attachment) -{ -} - -file_info::~file_info() {} - -const byte_array &file_info::attachment() const { return c_attachment; } - -void file_info::attachment(const byte_array &new_attachment) -{ c_attachment = new_attachment; } - -const astring &file_info::secondary() const { return c_secondary; } - -void file_info::secondary(const astring &new_sec) { c_secondary = new_sec; } - -astring file_info::text_form() const -{ - astring to_return = raw() - + a_sprintf(", size=%0.f, chksum=%d", _file_size, _checksum); - if (c_secondary.t()) - to_return += astring(", 2ndary=") + c_secondary; - return to_return; -} - -bool file_info::calculate(const astring &prefix, bool just_size, int checksum_edge) -{ - FUNCDEF("calculate"); - filename full; - if (prefix.t()) full = prefix + "/" + *this; - else full = *this; - if (!full.exists()) { -#ifdef DEBUG_FILE_INFO - LOG(astring("failed to find file: ") + full.raw()); -#endif - return false; - } - // get time again. - _time = file_time(full); - -//#ifdef DEBUG_FILE_INFO -// astring temptext; -// _time.text_form(temptext); -// LOG(astring("file calculate on ") + full.raw() + " time=" + temptext); -//#endif - - // open the file for reading. - huge_file to_read(full.raw(), "rb"); - if (!to_read.good()) { -#ifdef DEBUG_FILE_INFO - LOG(astring("file has non-good status: ") + full.raw()); -#endif - return false; // why did that happen? - } - // set the size appropriately. - _file_size = to_read.length(); - if (just_size) - return true; // done for that case. - - // now read the file and compute a checksum. - uint16 curr_sum = 0; // the current checksum being computed. - byte_array chunk; // temporary chunk of data from file. - -//hmmm: make this optimization (hack) optional! - - // this algorithm takes a chunk on each end of the file for checksums. - // this saves us from reading a huge amount of data, although it will be - // fooled if a huge binary file is changed only in the middle and has the - // same size as before. for most purposes, this is not a problem, although - // databases that are fixed size might fool us. if records are written in - // the middle without updating the head or tail sections, then we're hosed. - - bool skip_tail = false; // true if we don't need the tail piece. - double head_start = 0, head_end = 0, tail_start = 0, - tail_end = _file_size - 1; - if (_file_size <= double(2 * checksum_edge)) { - // we're applying a rule for when the file is too small compared to - // the chunk factor doubled; we'll just read the whole file. - head_end = _file_size - 1; - skip_tail = true; - } else { - // here we compute the ending of the head piece and the beginning of - // the tail piece. each will be about checksum_edge in size. - head_end = minimum(_file_size / 2, double(checksum_edge)) - 1; - tail_start = _file_size - minimum(_file_size / 2, double(checksum_edge)); - } - - // read the head end of the file. - int size_read = 0; - outcome ret = to_read.read(chunk, int(head_end - head_start + 1), size_read); - if (ret != huge_file::OKAY) { -#ifdef DEBUG_FILE_INFO - LOG(astring("reading file failed: ") + full.raw()); -#endif - return false; // failed to read. - } - curr_sum = checksums::rolling_fletcher_checksum(curr_sum, chunk.observe(), - chunk.length()); - - // read the tail end of the file. - if (!skip_tail) { - to_read.seek(tail_start, byte_filer::FROM_START); - ret = to_read.read(chunk, int(tail_end - tail_start + 1), size_read); - if (ret != huge_file::OKAY) { -#ifdef DEBUG_FILE_INFO - LOG(astring("reading tail of file failed: ") + full.raw()); -#endif - return false; // failed to read. - } - curr_sum = checksums::rolling_fletcher_checksum(curr_sum, chunk.observe(), - chunk.length()); - } - - _checksum = curr_sum; - return true; -} - -int file_info::packed_size() const -{ - return filename::packed_size() - + structures::packed_size(_file_size) - + _time.packed_size() - + PACKED_SIZE_INT32 - + c_secondary.packed_size() - + structures::packed_size(c_attachment); -} - -void file_info::pack(byte_array &packed_form) const -{ - filename::pack(packed_form); - attach(packed_form, _file_size); - _time.pack(packed_form); - attach(packed_form, _checksum); - c_secondary.pack(packed_form); - attach(packed_form, c_attachment); -} - -bool file_info::unpack(byte_array &packed_form) -{ - if (!filename::unpack(packed_form)) - return false; - if (!detach(packed_form, _file_size)) - return false; - if (!_time.unpack(packed_form)) - return false; - if (!detach(packed_form, _checksum)) - return false; - if (!c_secondary.unpack(packed_form)) - return false; - if (!detach(packed_form, c_attachment)) - return false; - return true; -} - -file_info &file_info::operator = (const file_info &to_copy) -{ - if (this == &to_copy) - return *this; - (filename &)(*this) = (filename &)to_copy; - c_attachment = to_copy.c_attachment; - _time = to_copy._time; - _file_size = to_copy._file_size; - c_secondary = to_copy.c_secondary; - _checksum = to_copy._checksum; - return *this; -} - -} //namespace. - diff --git a/core/library/filesystem/file_info.h b/core/library/filesystem/file_info.h deleted file mode 100644 index 0e29be49..00000000 --- a/core/library/filesystem/file_info.h +++ /dev/null @@ -1,96 +0,0 @@ -#ifndef FILE_INFO_CLASS -#define FILE_INFO_CLASS - -/*****************************************************************************\ -* * -* Name : file_info * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "filename.h" -#include "file_time.h" - -#include -#include - -namespace filesystem { - -//! Encapsulates some measures and calculations based on a file's contents. - -class file_info : public filename -{ -public: - //! this enum encapsulates how files may be compared. - enum file_similarity { - EQUAL_NAME = 0, // we assume name equality is pre-eminent and always required. - EQUAL_CHECKSUM = 0x1, // the files have the same checksum, however computed. - EQUAL_TIMESTAMP = 0x2, // the files have exactly equal timestamps. - EQUAL_FILESIZE = 0x4, // the files have the same sizes. - EQUAL_CHECKSUM_TIMESTAMP_FILESIZE = EQUAL_CHECKSUM & EQUAL_TIMESTAMP & EQUAL_FILESIZE - }; - - double _file_size; //!< the size of the file. - file_time _time; //!< the file's access time. - int _checksum; //!< the checksum for the file. - - file_info(); //!< blank constructor. - - file_info(const filename &to_copy, double file_size, - const file_time &time = file_time(), int checksum = 0); - //!< to get the real file size, timestamp and checksum, invoke the calculate method. - - file_info(const file_info &to_copy); - - virtual ~file_info(); - - DEFINE_CLASS_NAME("file_info"); - - file_info &operator = (const file_info &to_copy); - - basis::astring text_form() const; - - bool calculate(const basis::astring &prefix, bool just_size_n_time, - int checksum_edge = 1 * basis::KILOBYTE); - //!< fills in the correct file size and checksum information for this file. - /*!< note that the file must exist for this to work. if "just_size_n_time" - is true, then the checksum is not calculated. if the "prefix" is not - empty, then it is used as a directory path that needs to be added to - the filename to make it completely valid. this is common if the - filename is stored relative to a path. the "checksum_edge" is used for - checksum calculations; only 2 * checksum_edge bytes will be factored in, - each part from the head and tail ends of the file. */ - - const basis::astring &secondary() const; - //!< observes the alternate form of the name. - void secondary(const basis::astring &new_sec); - //!< accesses the alternate form of the name. - - const basis::byte_array &attachment() const; - //!< returns the chunk of data optionally attached to the file's info. - /*!< this supports extending the file's record with extra data that might - be needed during processing. */ - void attachment(const basis::byte_array &new_attachment); - //!< sets the optional chunk of data hooked up to the file's info. - - // standard streaming operations. - virtual int packed_size() const; - virtual void pack(basis::byte_array &packed_form) const; - virtual bool unpack(basis::byte_array &packed_form); - -private: - basis::astring c_secondary; //!< alternate filename for the main one. - basis::byte_array c_attachment; //!< extra information, if needed. -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/file_time.cpp b/core/library/filesystem/file_time.cpp deleted file mode 100644 index 5f05a4fb..00000000 --- a/core/library/filesystem/file_time.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/*****************************************************************************\ -* * -* Name : file_time * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "file_time.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef __UNIX__ - #include -#endif -#ifdef __WIN32__ - #include -#endif - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -using namespace basis; - -namespace filesystem { - -file_time::file_time() : _when(0) { reset(""); } - -file_time::file_time(FILE *the_FILE) : _when(0) { reset(the_FILE); } - -file_time::file_time(const time_t &t) : _when(t) {} - -file_time::file_time(const astring &filename) -: _when(0) -{ - FILE *ptr = fopen(filename.s(), "r"); - if (ptr) { - reset(ptr); - fclose(ptr); - } -} - -file_time::~file_time() {} - -bool file_time::set_time(const basis::astring &filename) -{ - // prepare the access time and modified time to match our stamp. - utimbuf held_time; - held_time.actime = raw(); - held_time.modtime = raw(); - // stuff our timestamp on the file. - return !utime(filename.s(), &held_time); -} - -void file_time::reset(const time_t &t) { _when = t; } - -void file_time::reset(const astring &filename) -{ - FILE *ptr = fopen(filename.s(), "r"); - if (ptr) { - reset(ptr); - fclose(ptr); - } -} - -void file_time::reset(FILE *the_FILE_in) -{ - FUNCDEF("reset"); - _when = 0; - FILE *the_file = (FILE *)the_FILE_in; - if (!the_file) { - return; - } - struct stat stat_buffer; - if (fstat(fileno(the_file), &stat_buffer) < 0) { - LOG("stat failure on file"); - } - _when = stat_buffer.st_mtime; -} - -int file_time::compare(const file_time &b) const -{ - if (_when > b._when) return 1; - else if (_when == b._when) return 0; - else return -1; -} - -bool file_time::less_than(const orderable &ft2) const -{ - const file_time *cast = dynamic_cast(&ft2); - if (!cast) return false; - return bool(compare(*cast) < 0); -} - -bool file_time::equal_to(const equalizable &ft2) const -{ - const file_time *cast = dynamic_cast(&ft2); - if (!cast) return false; - return bool(compare(*cast) == 0); -} - -void file_time::text_form(basis::base_string &time_string) const -{ - time_string.assign(basis::astring(class_name()) + basis::a_sprintf("@%x", _when)); -} - -void file_time::readable_text_form(basis::base_string &time_string) const -{ - tm *now = localtime(&_when); // convert to time round hereparts. - time_string.assign(a_sprintf("%d.%02d.%02d %02d:%02d:%02d", - now->tm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec)); -} - -// magic value unfortunately, but this is the length of a packed string with 10 characters. -// we do this because the size of 2^32 in decimal requires that many characters, and we also -// want to just have a fixed length chunk for this. we are not worried about larger pieces -// than that because we cannot conveniently handle them anyhow. -int file_time::packed_size() const { return 11; } - -void file_time::pack(byte_array &packed_form) const -{ a_sprintf("%010d", _when).pack(packed_form); } - -bool file_time::unpack(byte_array &packed_form) -{ - astring tempo; - if (!tempo.unpack(packed_form)) return false; - _when = tempo.convert(0L); - return true; -} - -} //namespace - diff --git a/core/library/filesystem/file_time.h b/core/library/filesystem/file_time.h deleted file mode 100644 index 0e89d356..00000000 --- a/core/library/filesystem/file_time.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef FILE_TIME_CLASS -#define FILE_TIME_CLASS - -/*****************************************************************************\ -* * -* Name : file_time * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! A platform independent way to obtain the timestamp of a file. - -#include -#include -#include - -#include -#include - -namespace filesystem { - -class file_time -: public virtual basis::hoople_standard, - public virtual basis::orderable -{ -public: - file_time(); //!< sets up a bogus file_time object. - - file_time(FILE *the_FILE); - //!< sets up the file_time information given a the file stream of interest. - /*!< If the stream is NIL, then the file_time is set up with an invalid - time. */ - - file_time(const basis::astring &filename); - //!< this constructor operates on a file's name rather than a FILE stream. - - file_time(const time_t &init); - //!< starts the file_time with a particular "init" time. - -//hmmm: need a converter that sucks in an earth_time time_locus object. - - virtual ~file_time(); - - DEFINE_CLASS_NAME("file_time"); - - virtual void text_form(basis::base_string &time_string) const; - //!< returns a definitive but sorta ugly version of the file's time. - - virtual void readable_text_form(basis::base_string &time_string) const; - //!< sets "time_string" to a human readable form of the file's time. - - void reset(FILE *the_FILE); - //!< reacquires the time from a different FILE than constructed with. - /*!< this also can connect a FILE to the file_time object after using the - empty constructor. further, it can also be used to refresh a file's time - to account for changes in its timestamp. */ - - void reset(const basis::astring &filename); - //!< parallel version of reset() takes a file name instead of a stream. - - void reset(const time_t &init); - //!< parallel version of reset() takes a time_t instead of a stream. - - time_t raw() const { return _when; } - //!< provides the OS version of the file's timestamp. - - bool set_time(const basis::astring &filename); - //!< sets the time for the the "filename" to the currently held time. - - // Standard comparison operators between this file time and the file time - // "ft2". These are meaningless if either time is invalid. - virtual bool less_than(const basis::orderable &ft2) const; - virtual bool equal_to(const basis::equalizable &ft2) const; - - // supports streaming the time into and out of a byte array. - virtual int packed_size() const; - virtual void pack(basis::byte_array &packed_form) const; - virtual bool unpack(basis::byte_array &packed_form); - -private: - time_t _when; //!< our record of the file's timestamp. - - int compare(const file_time &ft2) const; - //!< root comparison function for all the operators. -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/filename.cpp b/core/library/filesystem/filename.cpp deleted file mode 100644 index ef148ea8..00000000 --- a/core/library/filesystem/filename.cpp +++ /dev/null @@ -1,544 +0,0 @@ -/*****************************************************************************\ -* * -* Name : filename * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// implementation note: the filename is kept canonicalized. any constructor -// or assignment operator should ensure this (except the blank constructor). - -#include "filename.h" - -#include -#include - -#include -#include -#include -#ifdef __UNIX__ - #include -#endif -#ifdef __WIN32__ - #include -#endif - -using namespace basis; -using namespace structures; - -class status_info : public stat -{ -}; - -namespace filesystem { - -#if defined(__WIN32__) || defined(__VMS__) - const char DEFAULT_SEPARATOR = '\\'; -#elif defined(__UNIX__) - const char DEFAULT_SEPARATOR = '/'; -#else - #error "We have no idea what the default path separator is." -#endif - -const char *NO_PARENT_DEFAULT = "."; - // used when no directory name can be popped off. - -filename::filename() -: astring(), - _had_directory(false) -{} - -filename::filename(const astring &name) -: astring(name), - _had_directory(true) -{ canonicalize(); } - -filename::filename(const astring &directory, const astring &name_of_file) -: astring(directory), - _had_directory(true) -{ - // if the directory is empty, use the current directory. - if (!directory) { - *this = astring(NO_PARENT_DEFAULT); - _had_directory = false; - } - // check for a slash on the end of the directory. add one if there is none - // currently. - bool add_slash = false; - if ( (directory[directory.end()] != '\\') - && (directory[directory.end()] != '/') ) add_slash = true; - if (add_slash) *this += DEFAULT_SEPARATOR; - *this += name_of_file; - canonicalize(); -} - -filename::filename(const filename &to_copy) -: astring(to_copy), - _had_directory(to_copy._had_directory) -{ canonicalize(); } - -filename::~filename() {} - -astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); } - -astring &filename::raw() { return *this; } - -const astring &filename::raw() const { return *this; } - -bool filename::good() const { return exists(); } - -bool filename::unlink() const { return ::unlink(observe()) == 0; } - -astring filename::null_device() -{ -#ifdef __WIN32__ - return "null:"; -#else - return "/dev/null"; -#endif -} - -bool filename::separator(char is_it) -{ return (is_it == pc_separator) || (is_it == unix_separator); } - -filename &filename::operator = (const filename &to_copy) -{ - if (this == &to_copy) return *this; - (astring &)(*this) = to_copy; - _had_directory = to_copy._had_directory; - return *this; -} - -filename &filename::operator = (const astring &to_copy) -{ - _had_directory = true; - if (this == &to_copy) return *this; - (astring &)(*this) = to_copy; - canonicalize(); - return *this; -} - -astring filename::pop() -{ - astring to_return = basename(); - filename parent_dir = parent(); - if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) { - // we haven't gone anywhere. - return ""; // signal that nothing was removed. - } - *this = parent_dir; - return to_return; -} - -filename filename::parent() const { return dirname(); } - -void filename::push(const astring &to_push) -{ - *this = filename(*this, to_push); -} - -void filename::canonicalize() -{ - // turn all the non-default separators into the default. - bool found_sep = false; - for (int j = 0; j < length(); j++) { - if (separator(get(j))) { - found_sep = true; - put(j, DEFAULT_SEPARATOR); - } - } - - // if there wasn't a single directory separator, then they must not have - // specified any directory name for this filename (although it could itself - // be a directory). - if (!found_sep) _had_directory = false; - - // remove all occurrences of double separators except for the first - // double set, which could be a UNC filename. that's why the index below - // starts at one rather than zero. - bool saw_sep = false; - for (int i = 1; i < length(); i++) { - if (separator(get(i))) { - if (saw_sep) { - zap(i, i); - // two in a row is no good, except for the first two. - i--; // skip back one and try again. - continue; - } - saw_sep = true; - } else saw_sep = false; - } - - // we don't crop the last separator if the name's too small. for msdos - // names, that would be chopping a slash off the c:\ style name. - if (length() > 3) { - // zap any separators that are hiding on the end. - const int last = end(); - if (separator(get(last))) zap(last, last); - } else if ( (length() == 2) && (get(1) == ':') ) { - // special case for dos drive names. we turn it back into a valid - // directory rather than leaving it as just "X:". that form of the name - // means something else under dos/windows. - *this += astring(DEFAULT_SEPARATOR, 1); - } -} - -char filename::drive(bool interact_with_fs) const -{ - // first guess: if second letter's a colon, first letter's the drive. - if (length() < 2) - return '\0'; - if (get(1) == ':') - return get(0); - if (!interact_with_fs) - return '\0'; - - // otherwise, retrieve the file system's record for the file. - status_info fill; - if (!get_info(&fill)) - return '\0'; - return char('A' + fill.st_dev); -} - -astring filename::extension() const -{ - astring base(basename().raw()); - int posn = base.find('.', base.end(), true); - if (negative(posn)) - return ""; - return base.substring(posn + 1, base.length() - 1); -} - -astring filename::rootname() const -{ - astring base(basename().raw()); - int posn = base.find('.', base.end(), true); - if (negative(posn)) - return base; - return base.substring(0, posn - 1); -} - -bool filename::get_info(status_info *to_fill) const -{ - int ret = stat(observe(), to_fill); - if (ret) - return false; - return true; -} - -bool filename::is_directory() const -{ - status_info fill; - if (!get_info(&fill)) - return false; - return !!(fill.st_mode & S_IFDIR); -} - -bool filename::is_writable() const -{ - status_info fill; - if (!get_info(&fill)) - return false; - return !!(fill.st_mode & S_IWRITE); -} - -bool filename::is_readable() const -{ - status_info fill; - if (!get_info(&fill)) - return false; - return !!(fill.st_mode & S_IREAD); -} - -bool filename::is_executable() const -{ - status_info fill; - if (!get_info(&fill)) - return false; - return !!(fill.st_mode & S_IEXEC); -} - -int filename::find_last_separator(const astring &look_at) const -{ - int last_sep = -1; - int sep = 0; - while (sep >= 0) { - sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1); - if (sep >= 0) last_sep = sep; - } - return last_sep; -} - -filename filename::basename() const -{ - astring basename = *this; - int last_sep = find_last_separator(basename); - if (last_sep >= 0) basename.zap(0, last_sep); - return basename; -} - -filename filename::dirname() const -{ - astring dirname = *this; - int last_sep = find_last_separator(dirname); - // we don't accept ripping off the first slash. - if (last_sep >= 1) { - // we can rip the slash and suffix off to get the directory name. however, - // this might be in the form X: on windows. if they want the slash to - // remain, they can use the dirname that appends it. - dirname.zap(last_sep, dirname.end()); - } else { - if (get(0) == DEFAULT_SEPARATOR) { - // handle when we're up at the top of the filesystem. on unix, once - // you hit the root, you can keep going up but you still remain at - // the root. similarly on windoze, if there's no drive name in there. - dirname = astring(DEFAULT_SEPARATOR, 1); - } else { - // there's no slash at all in the filename any more. we assume that - // the directory is the current one, if no other information is - // available. this default is already used by some code. - dirname = NO_PARENT_DEFAULT; - } - } - return dirname; -} - -astring filename::dirname(bool add_slash) const -{ - astring tempname = dirname().raw(); - if (add_slash) tempname += DEFAULT_SEPARATOR; - return tempname; -} - -bool filename::exists() const -{ - if (is_directory()) - return true; - if (!length()) - return false; - return is_readable(); -/// byte_filer opened(observe(), "rb"); -/// return opened.good(); -} - -bool filename::legal_character(char to_check) -{ - switch (to_check) { - case ':': case ';': - case '\\': case '/': - case '*': case '?': case '$': case '&': case '|': - case '\'': case '"': case '`': - case '(': case ')': - case '[': case ']': - case '<': case '>': - case '{': case '}': - return false; - default: return true; - } -} - -void filename::detooth_filename(astring &to_clean, char replacement) -{ - for (int i = 0; i < to_clean.length(); i++) { - if (!legal_character(to_clean[i])) - to_clean[i] = replacement; - } -} - -int filename::packed_size() const -{ - return PACKED_SIZE_INT32 + astring::packed_size(); -} - -void filename::pack(byte_array &packed_form) const -{ - attach(packed_form, int(_had_directory)); - astring::pack(packed_form); -} - -bool filename::unpack(byte_array &packed_form) -{ - int temp; - if (!detach(packed_form, temp)) - return false; - _had_directory = temp; - if (!astring::unpack(packed_form)) - return false; - return true; -} - -void filename::separate(string_array &pieces) const -{ - pieces.reset(); - const astring &raw_form = raw(); - astring accumulator; // holds the names we find. - for (int i = 0; i < raw_form.length(); i++) { - if (separator(raw_form[i])) { - // this is a separator character, so eat it and add the accumulated - // string to the list. - if (!i || accumulator.length()) pieces += accumulator; - // now reset our accumulated text. - accumulator = astring::empty_string(); - } else { - // not a separator, so just accumulate it. - accumulator += raw_form[i]; - } - } - if (accumulator.length()) pieces += accumulator; -} - -void filename::join(const string_array &pieces) -{ - astring constructed_name; // we'll make a filename here. - for (int i = 0; i < pieces.length(); i++) { - constructed_name += pieces[i]; - if (!i || (i != pieces.length() - 1)) - constructed_name += DEFAULT_SEPARATOR; - } - *this = constructed_name; -} - -bool filename::base_compare_prefix(const filename &to_compare, - string_array &first, string_array &second) -{ - separate(first); - to_compare.separate(second); - // that case should never be allowed, since there are some bits missing - // in the name to be compared. - if (first.length() > second.length()) - return false; - - // compare each of the pieces. - for (int i = 0; i < first.length(); i++) { -#if defined(__WIN32__) || defined(__VMS__) - // case-insensitive compare. - if (!first[i].iequals(second[i])) - return false; -#else - // case-sensitive compare. - if (first[i] != second[i]) - return false; -#endif - } - return true; -} - -bool filename::compare_prefix(const filename &to_compare, astring &sequel) -{ - sequel = astring::empty_string(); // clean our output parameter. - string_array first; - string_array second; - if (!base_compare_prefix(to_compare, first, second)) - return false; - - // create the sequel string. - int extra_strings = second.length() - first.length(); - for (int i = second.length() - extra_strings; i < second.length(); i++) { - sequel += second[i]; - if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR; - } - - return true; -} - -bool filename::compare_prefix(const filename &to_compare) -{ - string_array first; - string_array second; - return base_compare_prefix(to_compare, first, second); -} - -bool filename::base_compare_suffix(const filename &to_compare, - string_array &first, string_array &second) -{ - separate(first); - to_compare.separate(second); - // that case should never be allowed, since there are some bits missing - // in the name to be compared. - if (first.length() > second.length()) - return false; - - // compare each of the pieces. - for (int i = first.length() - 1; i >= 0; i--) { -//clean up this computation; the difference in lengths is constant--use that. - int distance_from_end = first.length() - 1 - i; - int j = second.length() - 1 - distance_from_end; -#if defined(__WIN32__) || defined(__VMS__) - // case-insensitive compare. - if (!first[i].iequals(second[j])) - return false; -#else - // case-sensitive compare. - if (first[i] != second[j]) - return false; -#endif - } - return true; -} - -bool filename::compare_suffix(const filename &to_compare, astring &prequel) -{ - prequel = astring::empty_string(); // clean our output parameter. - string_array first; - string_array second; - if (!base_compare_suffix(to_compare, first, second)) - return false; - - // create the prequel string. - int extra_strings = second.length() - first.length(); - for (int i = 0; i < extra_strings; i++) { - prequel += second[i]; - if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR; - } - return true; -} - -bool filename::compare_suffix(const filename &to_compare) -{ - string_array first; - string_array second; - return base_compare_suffix(to_compare, first, second); -} - -bool filename::chmod(int write_mode, int owner_mode) const -{ - int chmod_value = 0; -#ifdef __UNIX__ - if (write_mode & ALLOW_READ) { - if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR; - if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP; - if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH; - } - if (write_mode & ALLOW_WRITE) { - if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR; - if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP; - if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH; - } -//// chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; -#elif defined(__WIN32__) - if (write_mode & ALLOW_READ) { - chmod_value |= _S_IREAD; - } - if (write_mode & ALLOW_WRITE) { - chmod_value |= _S_IWRITE; - } -#else - #error unsupported OS type currently. -#endif - int chmod_result = ::chmod(raw().s(), chmod_value); - if (chmod_result) { -// LOG(astring("there was a problem changing permissions on ") + raw()); - return false; - } - return true; -} - -} //namespace. - diff --git a/core/library/filesystem/filename.h b/core/library/filesystem/filename.h deleted file mode 100644 index a6018ef5..00000000 --- a/core/library/filesystem/filename.h +++ /dev/null @@ -1,247 +0,0 @@ -#ifndef FILENAME_CLASS -#define FILENAME_CLASS - -/*****************************************************************************\ -* * -* Name : filename * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -// forward declarations. -class status_info; - -//hmmm: this doesn't really belong here, does it. - -// define useful constant for filesystem path length. -#ifndef MAX_ABS_PATH - #ifdef __WIN32__ - #include - #define MAX_ABS_PATH MAX_PATH - #else - #ifdef __APPLE__ - #include - #else - #include - #endif - #define MAX_ABS_PATH PATH_MAX - #endif -#endif - - -namespace filesystem { - -//! Provides operations commonly needed on file names. - -class filename : public basis::astring, public virtual basis::packable -{ -public: - filename(); //!< blank constructor. - filename(const basis::astring &name); - //!< creates a filename from any part of a full pathname, if possible. - /*!< if the name contains quotes, they are stripped out. */ - filename(const basis::astring &directory, const basis::astring &name_of_file); - //!< constructs a filename from a "directory" and the "name_of_file". - /*!< the "name_of_file" can itself be a directory. */ - filename(const filename &to_copy); //!< copy constructor. - - virtual ~filename(); - - bool good() const; - //!< returns true if the filename seems to be valid. - /*!< this means that not only was the pathname parsed and found valid, - but the file actually exists. */ - - const basis::astring &raw() const; - //!< returns the astring that we're holding onto for the path. - basis::astring &raw(); - //!< accesses the astring that we're holding onto for the path. - /*!< important note: if you change the string with this non-const raw() - method, you MUST call canonicalize() on it again afterwards. */ - - filename &operator = (const filename &to_copy); - //!< provides assignment for this object, plus a simple string. - filename &operator = (const basis::astring &to_copy); - //!< provides assignment for this object, plus a simple string. - /*!< the latter version invokes canonicalize to clean the string up. */ - - void canonicalize(); - //!< cleans up the filename as needed for the current operating system. - /*!< reforms the name by replacing any alternate directory separators with - the operating system's preferred character. */ - - bool exists() const; - //!< returns true if the file exists. - - bool unlink() const; - //!< actually removes the file, if possible. - /*!< if the file was successfully deleted, then true is returned. */ - - filename parent() const; - //!< returns the parent filename for this one. - - basis::astring pop(); - //!< removes the deepest component of the pathname. - /*!< the component might be a file or directory name, but popping beyond - the top-level directory will not succeed. the returned string contains - the component that was removed. it will be a blank string if nothing - could be popped. */ - - void push(const basis::astring &to_push); - //!< pushes a new filename onto the current pathname. - /*!< this only makes sense as a real pathname if this is currently a - directory name and the component "to_push" is a child of that directory - (or one intends to create that component as a child). this is the - opposite of pop. */ - - filename basename() const; - //!< returns the base of the filename; no directory. - filename dirname() const; - //!< returns the directory for the filename. - /*!< if no directory name can be found in the filename, then "." is - returned. */ - basis::astring dirname(bool add_slash) const; - //!< returns the directory for the filename and optionally adds a slash. - /*!< if "add_slash" is true, then the default directory separator will be - present on the end of the string. */ - bool had_directory() const { return _had_directory; } - //!< returns true if the name that we were given had a non-empty directory. - /*!< this allows one to distinguish between a file with the current - directory (.) attached and a file with no directory specified. */ - - char drive(bool interact_with_fs = false) const; - //!< returns the drive letter for the file, without the colon. - /*!< this only makes sense for a fully qualified MS-DOS style name. if no - drive letter is found, then '\0' is returned. if "interact_with_fs" is - true, then the file system will be checked for the actual drive if no - drive letter was found in the contents. */ - - basis::astring extension() const; - //!< returns the extension for the file, if one is present. - - basis::astring rootname() const; - //!< returns the root part of the basename without an extension. - - // status functions return true if the characteristic embodied in - // the name is also true. - - bool is_directory() const; - bool is_writable() const; - bool is_readable() const; - bool is_executable() const; - - enum write_modes { - ALLOW_NEITHER = 0x0, - ALLOW_READ = 0x1, ALLOW_WRITE = 0x2, - ALLOW_BOTH = ALLOW_READ | ALLOW_WRITE - }; - - enum ownership_modes { - NO_RIGHTS = 0x0, - USER_RIGHTS = 0x1, GROUP_RIGHTS = 0x2, OTHER_RIGHTS = 0x4, - ALL_RIGHTS = USER_RIGHTS | GROUP_RIGHTS | OTHER_RIGHTS - }; - - bool chmod(int write_mode, int owner_mode) const; - //!< changes the access rights on the file. - - //! the default separator for directories per operating system. - /*! the PC uses the backward slash to separate file and directory names from - each other, while Unix uses the forward slash. */ - enum directory_separator { pc_separator = '\\', unix_separator = '/' }; - - static bool separator(char is_it); - //!< returns true if the character "is_it" in question is a separator. - - static basis::astring default_separator(); - //!< returns the default separator character for this OS. - - static bool legal_character(char to_check); - //!< returns true if "to_check" is a valid character in a filename. - /*!< this does not consider separator characters; it only looks at the - the name components. also, it is appropriate for the union of the - operating systems we support. */ - - static void detooth_filename(basis::astring &to_clean, char replacement = '_'); - //!< takes any known illegal file system characters out of "to_clean". - /*!< this prepares "to_clean" for use as a component in a larger filename - by ensuring that the file system will not reject the name (as long as a - suitable directory path is prepended to the name and permissions allow - the file to be created or accessed). the "replacement" is used as the - character that is substituted instead of illegal characters. */ - - void separate(structures::string_array &pieces) const; - //!< breaks the filename into its component directories. - /*!< this returns an array containing the component names. the last - component, unless the filename held is actually a directory, should be the - name of the file. if the first character is a directory, then the first - component will be empty. */ - - void join(const structures::string_array &pieces); - //!< undoes a separate() operation to get the filename back. - /*!< "this" is set to a filename made from each of the "pieces". if there - are any directory separators inside the pieces, then they will be removed - by canonicalize(). */ - - // these implement the packing functionality. - virtual void pack(basis::byte_array &packed_form) const; - virtual bool unpack(basis::byte_array &packed_form); - virtual int packed_size() const; - - bool compare_prefix(const filename &to_compare, basis::astring &sequel); - //!< examines "this" filename to see if it's a prefix of "to_compare". - /*!< this returns true if all of "this" is the same as the first portion - of "to_compare". that is, if "this" is a prefix of "to_compare", then - true is returned. this will always fail if there are fewer components in - "to_compare". it will always succeed if the two filenames are identical. - on success, the "sequel" is set to the portion of "to_compare" that's - not included in this filename. */ - - bool compare_prefix(const filename &to_compare); - //!< this simpler form doesn't bother with computing the sequel. - - bool compare_suffix(const filename &to_compare, basis::astring &prequel); - //!< compares the back end of a filename to this. - /*!< this is similar to compare_prefix() but it checks to see if the - back end of "this" filename is the same as "to_compare". if "this" is - longer than "to_compare", then failure occurs. only if all of the bits - in "this" are seen in the back of "to_compare" is true returned. */ - - bool compare_suffix(const filename &to_compare); - - static basis::astring null_device(); - //!< returns the name for the black hole device that consumes all input, i.e. /dev/null. - -private: - bool _had_directory; //!< true if _some_ directory was specified on init. -/// basis::astring *_contents; //!< the full path is held here. - - int find_last_separator(const basis::astring &look_at) const; - //!< locates the last separator character in the filename. - - bool get_info(status_info *to_fill) const; - //!< returns information for the filename. - - // helper functions do the real work for comparing. - bool base_compare_prefix(const filename &to_compare, structures::string_array &first, - structures::string_array &second); - bool base_compare_suffix(const filename &to_compare, structures::string_array &first, - structures::string_array &second); -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/filename_list.cpp b/core/library/filesystem/filename_list.cpp deleted file mode 100644 index 24d1522e..00000000 --- a/core/library/filesystem/filename_list.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/*****************************************************************************\ -* * -* Name : filename_list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "filename_list.h" - -#include -#include - -using namespace basis; -using namespace structures; -using namespace textual; - -namespace filesystem { - -filename_list::filename_list() : amorph() {} - -filename_list &filename_list::operator =(const filename_list &to_copy) -{ - if (this == &to_copy) return *this; - reset(); - for (int i = 0; i < to_copy.elements(); i++) { - append(new file_info(*to_copy.get(i))); - } - return *this; -} - -int filename_list::total_files() const { return elements(); } - -int filename_list::packed_size() const -{ return amorph_packed_size(*this); } - -void filename_list::pack(byte_array &packed_form) const -{ amorph_pack(packed_form, *this); } - -bool filename_list::unpack(byte_array &packed_form) -{ return amorph_unpack(packed_form, *this); } - -double filename_list::total_size() const -{ - double to_return = 0; - for (int i = 0; i < elements(); i++) - to_return += get(i)->_file_size; - return to_return; -} - -bool filename_list::calculate_progress(const filename &file, - double current_offset, int ¤t_file, double ¤t_size) -{ - current_file = 0; - current_size = 0; - int posn = locate(file); - if (negative(posn)) { - if (file.raw().t()) return false; // not a member. - // they're at the start of the run. - current_file = 1; - return true; - } - current_file = posn + 1; // position zero in array means file number 1. - double size_finished = 0; - // iterate on all files before the current one. - for (int i = 0; i < posn; i++) { - size_finished += get(i)->_file_size; - } - current_size = size_finished + current_offset; - return true; -} - -filename_list &filename_list::operator = (const string_array &to_copy) -{ - reset(); - for (int i = 0; i < to_copy.length(); i++) { - append(new file_info(to_copy[i], 0)); - } - return *this; -} - -void filename_list::fill(string_array &to_fill) -{ - to_fill.reset(); - for (int i = 0; i < elements(); i++) { - to_fill += get(i)->raw(); - } -} - -const file_info *filename_list::find(const filename &to_check) const -{ - for (int i = 0; i < elements(); i++) { -#if defined(__WIN32__) || defined(__VMS__) - if (to_check.raw().iequals(get(i)->raw())) return get(i); -#else - if (to_check.raw() == get(i)->raw()) return get(i); -#endif - } - return NIL; -} - -int filename_list::locate(const filename &to_find) const -{ - for (int i = 0; i < elements(); i++) { -#if defined(__WIN32__) || defined(__VMS__) - if (to_find.raw().iequals(get(i)->raw())) return i; -#else - if (to_find.raw() == get(i)->raw()) return i; -#endif - } - return common::NOT_FOUND; -} - -bool filename_list::member(const filename &to_check) const -{ - for (int i = 0; i < elements(); i++) { -#if defined(__WIN32__) || defined(__VMS__) - if (to_check.raw().iequals(get(i)->raw())) return true; -#else - if (to_check.raw() == get(i)->raw()) return true; -#endif - } - return false; -} - -bool filename_list::member_with_state(const file_info &to_check, file_info::file_similarity how) -{ - for (int i = 0; i < elements(); i++) { -#if defined(__WIN32__) || defined(__VMS__) - if (to_check.raw().iequals(get(i)->raw())) { -#else - if (to_check.raw() == get(i)->raw()) { -#endif - // once we have matched a name, the other checks will cause us to - // reject any other potential matches after this one if the requested - // check fails. - if ((how & file_info::EQUAL_FILESIZE) && (to_check._file_size != get(i)->_file_size) ) - return false; - if ((how & file_info::EQUAL_TIMESTAMP) && (to_check._time != get(i)->_time) ) - return false; - if ((how & file_info::EQUAL_CHECKSUM) && (to_check._checksum != get(i)->_checksum) ) - return false; - return true; - } - } - return false; -} - -astring filename_list::text_form() const -{ - astring to_return; -//hmmm: a length limit might be nice? - for (int i = 0; i < elements(); i++) { - to_return += a_sprintf("%d. ", i + 1); - if (get(i)) - to_return += get(i)->text_form(); - if (i != elements() - 1) - to_return += parser_bits::platform_eol_to_chars(); - } - return to_return; -} - -} //namespace. - diff --git a/core/library/filesystem/filename_list.h b/core/library/filesystem/filename_list.h deleted file mode 100644 index f5834d79..00000000 --- a/core/library/filesystem/filename_list.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef FILENAME_LIST_CLASS -#define FILENAME_LIST_CLASS - -/*****************************************************************************\ -* * -* Name : filename_list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! Implements a list of filenames. -/*! - This is based on an amorph so that adding to the list is efficient. - The underlying type held is actually a file_info rather than a filename. - This should not impose much extra overhead. - - Note: this is a heavyweight header; it shouldn't be used in other headers. -*/ - -#include "file_info.h" - -#include -#include - -namespace filesystem { - -class filename_list -: public structures::amorph, public virtual basis::packable -{ -public: - filename_list(); - - filename_list &operator =(const filename_list &to_copy); - - int total_files() const; - //!< returns the number of files currently held in the list. - - double total_size() const; - //!< returns the full size of all files listed. - - bool calculate_progress(const filename &file, double current_offset, - int ¤t_file, double ¤t_size); - //!< given the last "file" and position, this returns current positioning. - /*!< the "current_file" is set to the index of the "file" in the list - (using 1-based numbering for a 1,2,3,... series), and the "current_size" - is set to the total amount of bytes processed so far. */ - - filename_list &operator = (const structures::string_array &to_copy); - - void fill(structures::string_array &to_fill); - //!< stuffs the array "to_fill" with the filesnames from our list. - - const file_info *find(const filename &to_check) const; - //!< locates the record of information for the filename "to_check". - /*!< do not modify the returned object. it contains the current state - of the file in question. if the file wasn't in the list, then NIL is - returned. */ - - int locate(const filename &to_find) const; - //! finds the index for "to_find" or returns a negative number. - - bool member(const filename &to_check) const; - //!< returns true if "to_check" is listed here. - - bool member_with_state(const file_info &to_check, file_info::file_similarity comparison_method); - //!< returns true if the file "to_check" exists in the list with appropriate equivalence. - /*!< this will fail if the file name isn't present at all. and then it will also not return - true if the size is different (when EQUAL_FILESIZE is true), when the timestamp is different - (when EQUAL_TIMESTAMP is true), and when the checksum is different (you get the idea). */ - - basis::astring text_form() const; - - virtual int packed_size() const; - - virtual void pack(basis::byte_array &packed_form) const; - - virtual bool unpack(basis::byte_array &packed_form); -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/filename_tree.cpp b/core/library/filesystem/filename_tree.cpp deleted file mode 100644 index 04a42030..00000000 --- a/core/library/filesystem/filename_tree.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/*****************************************************************************\ -* * -* Name : filename_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "filename_tree.h" - -#include - -using namespace basis; -using namespace nodes; -using namespace structures; - -namespace filesystem { - -nodes::packable_tree *fname_tree_creator::create() { return new filename_tree; } - -////////////// - -filename_tree::filename_tree() : _depth(0) {} - -filename_tree::~filename_tree() { _dirname = ""; _files.reset(); } - -int filename_tree::packed_size() const { - return PACKED_SIZE_INT32 + _dirname.packed_size() + _files.packed_size(); -} - -void filename_tree::pack(byte_array &packed_form) const { - structures::attach(packed_form, _depth); - _dirname.pack(packed_form); - _files.pack(packed_form); -} - -bool filename_tree::unpack(byte_array &packed_form) { - if (!structures::detach(packed_form, _depth)) return false; - if (!_dirname.unpack(packed_form)) return false; - if (!_files.unpack(packed_form)) return false; - return true; -} - -} //namespace. - - diff --git a/core/library/filesystem/filename_tree.h b/core/library/filesystem/filename_tree.h deleted file mode 100644 index 7d857d06..00000000 --- a/core/library/filesystem/filename_tree.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef FILENAME_TREE_CLASS -#define FILENAME_TREE_CLASS - -/*****************************************************************************\ -* * -* Name : filename_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! This is a support class for the directory_tree. -/*! - This class has been exported from directory_tree's implementation to - avoid redundant code for iteration and such. - Note: this is a heavy-weight header that should not be included in other - headers. -*/ - -#include "filename_list.h" - -#include - -namespace filesystem { - -class filename_tree : public nodes::packable_tree -{ -public: - filename _dirname; //!< the full directory name at this position. - filename_list _files; //!< the filenames that are at this node in the tree. - int _depth; //!< how far below root node are we. - - filename_tree(); - - virtual ~filename_tree(); - - virtual int packed_size() const; - - virtual void pack(basis::byte_array &packed_form) const; - - virtual bool unpack(basis::byte_array &packed_form); -}; - -//! this is the tree factory used in the recursive_unpack. -/*! it meets our needs for regenerating these objects from a streamed form. */ -class fname_tree_creator : public nodes::packable_tree_factory -{ -public: -//// virtual ~fname_tree_creator() {} - virtual nodes::packable_tree *create(); - //!< implements the create() method for filename_trees to support packing. -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/heavy_file_ops.cpp b/core/library/filesystem/heavy_file_ops.cpp deleted file mode 100644 index 1e63ba31..00000000 --- a/core/library/filesystem/heavy_file_ops.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/*****************************************************************************\ -* * -* Name : heavy file operations * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "directory.h" -#include "filename.h" -#include "filename_list.h" -#include "heavy_file_ops.h" -#include "huge_file.h" - -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace filesystem { - -//#define DEBUG_HEAVY_FILE_OPS - // uncomment for noisier debugging. - -#undef LOG -#ifdef DEBUG_HEAVY_FILE_OPS - #include - #define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) -#else - #define LOG(s) {if(!!s){}} -#endif - -////////////// - -file_transfer_header::file_transfer_header(const file_time &time_stamp) -: _filename(), - _byte_start(0), - _length(0), - _time(time_stamp) -{ -} - -astring file_transfer_header::text_form() const -{ - astring time_text; - _time.text_form(time_text); - return astring("file=") + _filename - + a_sprintf(" start=%d len=%d stamp=", _byte_start, _length) - + time_text; -} - -astring file_transfer_header::readable_text_form() const -{ - astring time_text; - _time.readable_text_form(time_text); - return _filename - + a_sprintf(" [%d bytes, mod ", _length) - + time_text + "]"; -} - -void file_transfer_header::pack(byte_array &packed_form) const -{ - _filename.pack(packed_form); - attach(packed_form, _byte_start); - attach(packed_form, _length); - _time.pack(packed_form); -} - -bool file_transfer_header::unpack(byte_array &packed_form) -{ - if (!_filename.unpack(packed_form)) return false; - if (!detach(packed_form, _byte_start)) return false; - if (!detach(packed_form, _length)) return false; - if (!_time.unpack(packed_form)) return false; - return true; -} - -int file_transfer_header::packed_size() const -{ -byte_array temp; -attach(temp, _byte_start); -//hmmm: really ugly above; we should get a more exact way to know the size of -// packed doubles. - return _filename.length() + 1 - + temp.length() - + sizeof(int) - + _time.packed_size(); -} - -////////////// - -const size_t heavy_file_operations::COPY_CHUNK_FACTOR = 1 * MEGABYTE; - -size_t heavy_file_operations::copy_chunk_factor() -{ return COPY_CHUNK_FACTOR; } - -heavy_file_operations::~heavy_file_operations() {} - // we only need this due to our use of the root_object class_name support. - -const char *heavy_file_operations::outcome_name(const outcome &to_name) -{ - switch (to_name.value()) { - case SOURCE_MISSING: return "SOURCE_MISSING"; - case TARGET_ACCESS_ERROR: return "TARGET_ACCESS_ERROR"; - case TARGET_DIR_ERROR: return "TARGET_DIR_ERROR"; - default: return common::outcome_name(to_name); - } -} - -outcome heavy_file_operations::copy_file(const astring &source, - const astring &destination, int copy_chunk_factor) -{ -#ifdef DEBUG_HEAVY_FILE_OPS - FUNCDEF("copy_file"); -#endif - // check that the source exists... - filename source_path(source); - if (!source_path.exists()) return SOURCE_MISSING; - file_time source_time(source_path); // get the time on the source. - - // make sure the target directory exists... - filename target_path(destination); - filename targ_dir = target_path.dirname(); - if (!directory::recursive_create(targ_dir.raw())) return TARGET_DIR_ERROR; - - // open the source for reading. - huge_file source_file(source, "rb"); - if (!source_file.good()) return SOURCE_MISSING; -//hmmm: could be source is not accessible instead. - - // open target file for writing. - huge_file target_file(destination, "wb"); - if (!target_file.good()) return TARGET_ACCESS_ERROR; - - byte_array chunk; - int bytes_read = 0; - outcome ret; - while ( (ret = source_file.read(chunk, copy_chunk_factor, bytes_read)) - == huge_file::OKAY) { - int bytes_stored; - ret = target_file.write(chunk, bytes_stored); - if (bytes_stored != bytes_read) return TARGET_ACCESS_ERROR; - if (source_file.eof()) break; // time to escape. - } - - // set the time on the target file from the source's time. - source_time.set_time(target_path); - -#ifdef DEBUG_HEAVY_FILE_OPS - astring time; - source_time.text_form(time); - LOG(astring("setting file time for ") + source + " to " + time); -#endif - - return OKAY; -} - -outcome heavy_file_operations::write_file_chunk(const astring &target, - double byte_start, const byte_array &chunk, bool truncate, - int copy_chunk_factor) -{ -#ifdef DEBUG_HEAVY_FILE_OPS -// FUNCDEF("write_file_chunk"); -#endif - if (byte_start < 0) return BAD_INPUT; - - filename targ_name(target); - if (!directory::recursive_create(targ_name.dirname().raw())) - return TARGET_DIR_ERROR; - - if (!targ_name.exists()) { - huge_file target_file(target, "w"); - } - - huge_file target_file(target, "r+b"); - // open the file for updating (either read or write). - if (!target_file.good()) return TARGET_ACCESS_ERROR; - double curr_len = target_file.length(); - - if (curr_len < byte_start) { - byte_array new_chunk; - while (curr_len < byte_start) { - target_file.seek(0, byte_filer::FROM_END); // go to the end of the file. - new_chunk.reset(minimum(copy_chunk_factor, - int(curr_len - byte_start + 1))); - int written; - outcome ret = target_file.write(new_chunk, written); - if (written < new_chunk.length()) return TARGET_ACCESS_ERROR; - curr_len = target_file.length(); - } - } - target_file.seek(byte_start, byte_filer::FROM_START); - // jump to the proper location in the file. - int wrote; - outcome ret = target_file.write(chunk, wrote); - if (wrote != chunk.length()) return TARGET_ACCESS_ERROR; - if (truncate) { - target_file.truncate(); - } - return OKAY; -} - -bool heavy_file_operations::advance(const filename_list &to_transfer, - file_transfer_header &last_action) -{ -#ifdef DEBUG_HEAVY_FILE_OPS - FUNCDEF("advance"); -#endif - int indy = to_transfer.locate(last_action._filename); - if (negative(indy)) return false; // error. - if (indy == to_transfer.elements() - 1) return false; // done. - const file_info *currfile = to_transfer.get(indy + 1); - last_action._filename = currfile->raw(); - last_action._time = currfile->_time; - -#ifdef DEBUG_HEAVY_FILE_OPS - if (currfile->_time == file_time(time_t(0))) - LOG(astring("failed for ") + currfile->raw() + " -- has zero file time"); -#endif - - last_action._byte_start = 0; - last_action._length = 0; - return true; -} - -outcome heavy_file_operations::buffer_files(const astring &source_root, - const filename_list &to_transfer, file_transfer_header &last_action, - byte_array &storage, int maximum_bytes) -{ -#ifdef DEBUG_HEAVY_FILE_OPS -// FUNCDEF("buffer_files"); -#endif - storage.reset(); // clear out the current contents. - - if (!to_transfer.elements()) { - // we seem to be done. - return OKAY; - } - - outcome to_return = OKAY; - - // start filling the array with bytes from the files. - while (storage.length() < maximum_bytes) { - double remaining_in_array = maximum_bytes - storage.length() - - last_action.packed_size(); - if (remaining_in_array < 128) { - // ensure that we at least have a reasonable amount of space left - // for storing into the array. - break; - } - - // find the current file we're at, as provided in record. - if (!last_action._filename) { - // no filename yet. assume this is the first thing we've done. - const file_info *currfile = to_transfer.get(0); - last_action._filename = currfile->raw(); - last_action._time = currfile->_time; - last_action._byte_start = 0; - last_action._length = 0; - } - - const file_info *found = to_transfer.find(last_action._filename); - if (!found) { - // they have referenced a file that we don't have. that's bad news. - return BAD_INPUT; - } - - astring full_file = source_root + "/" + last_action._filename; - huge_file current(full_file, "rb"); - if (!current.good()) { - // we need to skip this file. - if (!advance(to_transfer, last_action)) break; - continue; - } - - if ((last_action._byte_start + last_action._length >= current.length()) - && current.length()) { - // this file is done now. go to the next one. - if (!advance(to_transfer, last_action)) break; - continue; - } - - // calculate the largest piece remaining of that file that will fit in the - // allotted space. - double new_start = last_action._byte_start + last_action._length; - double remaining_in_file = current.length() - new_start; - if (remaining_in_file < 0) remaining_in_file = 0; - double new_len = minimum(remaining_in_file, remaining_in_array); - - // pack this new piece of the file. - current.seek(new_start, byte_filer::FROM_START); - byte_array new_chunk; - int bytes_read = 0; - outcome ret = current.read(new_chunk, int(new_len), bytes_read); - if (bytes_read != new_len) { - if (!bytes_read) { - // some kind of problem reading the file. - if (!advance(to_transfer, last_action)) break; - continue; - } -//why would this happen? just complain, i guess. - } - - // update the record since it seems we're successful here. - last_action._byte_start = new_start; - last_action._length = int(new_len); - - // add in this next new chunk of file. - last_action.pack(storage); // add the header. - storage += new_chunk; // add the new stuff. - - if (!current.length()) { - // ensure we don't get stuck redoing zero length files, which we allowed - // to go past their end above (since otherwise we'd never see them). - if (!advance(to_transfer, last_action)) break; - continue; - } - - // just keep going, if there's space... - } - - return to_return; -} - -} //namespace. - diff --git a/core/library/filesystem/heavy_file_ops.h b/core/library/filesystem/heavy_file_ops.h deleted file mode 100644 index add49683..00000000 --- a/core/library/filesystem/heavy_file_ops.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef HEAVY_FILE_OPERATIONS_CLASS -#define HEAVY_FILE_OPERATIONS_CLASS - -/*****************************************************************************\ -* * -* Name : heavy file operations * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "filename_list.h" - -#include -#include - -namespace filesystem { - -//! describes one portion of an ongoing file transfer. -/*! this is just a header describing an attached byte package. it is expected -that the bytes follow this in the communication stream. */ - -class file_transfer_header : public basis::packable -{ -public: - basis::astring _filename; //!< the name of the file being transferred. - double _byte_start; //!< the starting location in the file being sent. - int _length; //!< the length of the transferred piece. - file_time _time; //!< the timestamp on the file. - - DEFINE_CLASS_NAME("file_transfer_header"); - - file_transfer_header(const file_time &time_stamp); - //!< refactored to force addition of the time_stamp. - - virtual void pack(basis::byte_array &packed_form) const; - virtual bool unpack(basis::byte_array &packed_form); - - virtual int packed_size() const; - - basis::astring text_form() const; - -//hmmm: this could live in lots of other places. file_info for one. - basis::astring readable_text_form() const; - //!< a nicer formatting of the information. -}; - -////////////// - -//! Provides serious file operations, such as copy and partial writing. - -class heavy_file_operations : public virtual basis::root_object -{ -public: - virtual ~heavy_file_operations(); - - enum outcomes { - OKAY = basis::common::OKAY, - BAD_INPUT = basis::common::BAD_INPUT, -// GARBAGE = basis::common::GARBAGE, -// NOT_FOUND = basis::common::NOT_FOUND, -// NONE_READY = basis::common::NONE_READY, -// FAILURE = basis::common::FAILURE, - DEFINE_OUTCOME(SOURCE_MISSING, -43, "The source file is not accessible"), - DEFINE_OUTCOME(TARGET_DIR_ERROR, -44, "The target's directory could not " - "be created"), - DEFINE_OUTCOME(TARGET_ACCESS_ERROR, -45, "The target file could not be " - "created") - }; - static const char *outcome_name(const basis::outcome &to_name); - - DEFINE_CLASS_NAME("heavy_file_operations"); - - static const size_t COPY_CHUNK_FACTOR; - //!< the default copy chunk size for the file copy method. - static size_t copy_chunk_factor(); - //!< method can be exported for use by shared libs. - - static basis::outcome copy_file(const basis::astring &source, const basis::astring &destination, - int copy_chunk_factor = copy_chunk_factor()); - //!< copies a file from the "source" location to the "destination". - /*!< the outcomes could be from this class or from common::outcomes. - the "copy_chunk_factor" is the read buffer size to use while copying. */ - - static basis::outcome write_file_chunk(const basis::astring &target, double byte_start, - const basis::byte_array &chunk, bool truncate = true, - int copy_chunk_factor = copy_chunk_factor()); - //!< stores a chunk of bytes into the "target" file. - /*!< writes the content stored in "chunk" into the file "target" at the - position "byte_start". the entire "chunk" will be used, which means the - file will either be that much larger or have the space between byte_start - and (byte_start + chunk.length() - 1) replaced. if the file is not yet as - large as "byte_start", then it will be expanded appropriately. if - "truncate" is true, then any contents past the new chunk are dropped from - the file. */ - - static basis::outcome buffer_files(const basis::astring &source_root, - const filename_list &to_transfer, file_transfer_header &last_action, - basis::byte_array &storage, int maximum_bytes); - //!< reads files in "to_transfer" and packs them into a "storage" buffer. - /*!< the maximum size allowed in storage is "maximum_bytes". the record - of the last file piece stored in "last_action" allows the next chunk - to be sent in subsequent calls. note that the buffer "storage" is cleared - out before bytes are stored into it; this is not an additive operation. */ - -private: - static bool advance(const filename_list &to_transfer, file_transfer_header &last_action); - //!< advances to the next file in the transfer list "to_transfer". -}; - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/filesystem/huge_file.cpp b/core/library/filesystem/huge_file.cpp deleted file mode 100644 index 4c218c56..00000000 --- a/core/library/filesystem/huge_file.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/*****************************************************************************\ -* * -* Name : huge_file * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_filer.h" -#include "huge_file.h" - -#include -#include -#include - -#include - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -//#define DEBUG_HUGE_FILE - // uncomment for noisy version. - -using namespace basis; - -namespace filesystem { - -huge_file::huge_file(const astring &filename, const astring &permissions) -: _real_file(new byte_filer(filename, permissions)), - _file_pointer(0) -{ -} - -huge_file::~huge_file() -{ - WHACK(_real_file); -} - -void huge_file::flush() { _real_file->flush(); } - -bool huge_file::truncate() { return _real_file->truncate(); } - -double huge_file::length() -{ - FUNCDEF("length"); - -//trying to read to see if we're past endpoint. -// if this approach works, length may want to close and reopen file for -// reading, since we can't add any bytes to it for writing just to find -// the length out. - - - double save_posn = _file_pointer; - // skip to the beginning of the file so we can try to find the end. - _file_pointer = 0; - _real_file->seek(0, byte_filer::FROM_START); - size_t naive_size = _real_file->length(); - if (naive_size < _real_file->file_size_limit()) { - // lucked out; we are within normal file size limitations. - seek(save_posn, byte_filer::FROM_START); - return double(naive_size); - } - - double best_highest = 0.0; // the maximum we've safely seeked to. - - size_t big_jump = byte_filer::file_size_limit(); - // try with the largest possible seek at first. - - while (true) { -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf("best highest=%.0f", best_highest)); -#endif - // iterate until we reach our exit condition, which seems like it must - // always occur eventually unless the file is being monkeyed with. - bool seek_ret = _real_file->seek(int(big_jump), byte_filer::FROM_CURRENT); -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" seek ret=%d", int(seek_ret))); -#endif - byte_array temp_bytes; - int bytes_read = _real_file->read(temp_bytes, 1); - if (bytes_read < 1) - seek_ret = false; -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" read %d bytes", bytes_read)); -#endif - bool at_eof = _real_file->eof(); -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" at_eof=%d", int(at_eof))); -#endif - if (seek_ret && !at_eof) { -#ifdef DEBUG_HUGE_FILE - LOG("seek worked, incrementing best highest and trying same jump again"); -#endif - // the seek worked, so we'll just jump forward again. - best_highest += double(big_jump); - _file_pointer += double(big_jump); - continue; - } else if (seek_ret && at_eof) { -#ifdef DEBUG_HUGE_FILE - LOG("seek worked but found eof exactly."); -#endif - // the seek did worked, but apparently we've also found the end point. - best_highest += double(big_jump); - _file_pointer += double(big_jump); - break; - } else { - // that seek was too large, so we need to back down and try a smaller - // seek size. -#ifdef DEBUG_HUGE_FILE - LOG("seek failed, going back to best highest and trying same jump again"); -#endif - _file_pointer = 0; - _real_file->seek(0, byte_filer::FROM_START); - outcome worked = seek(best_highest, byte_filer::FROM_START); - // this uses our version to position at large sizes. - if (worked != OKAY) { - // this is a bad failure; it says that the file size changed or - // something malfunctioned. we should always be able to get back to - // the last good size we found if the file is static. - LOG(a_sprintf("failed to seek back to best highest %.0f on ", - best_highest) + _real_file->filename()); - // try to repair our ideas about the file by starting the process - // over. -//hmmm: count the number of times restarted and bail after N. - seek_ret = _real_file->seek(0, byte_filer::FROM_START); - _file_pointer = 0; - if (!seek_ret) { - // the heck with this. we can't even go back to the start. this - // file seems to be screwed up now. - LOG(astring("failed to seek back to start of file! on ") - + _real_file->filename()); - return 0; - } - // reset the rest of the positions for our failed attempt to return - // to what we already thought was good. - _file_pointer = 0; - big_jump = byte_filer::file_size_limit(); - best_highest = 0; - continue; - } - // okay, nothing bad happened when we went back to our last good point. - if (big_jump <= 0) { - // success in finding the smallest place that we can't seek between. -#ifdef DEBUG_HUGE_FILE - LOG("got down to smallest big jump, 0!"); -#endif - break; - } - // formula expects that the maximum file size is a power of 2. - big_jump /= 2; -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf("restraining big jump down to %u.", big_jump)); -#endif - continue; - } - } - - // go back to where we started out. - seek(0, byte_filer::FROM_START); - seek(save_posn, byte_filer::FROM_CURRENT); -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf("saying file len is %.0f.", best_highest + 1.0)); -#endif - return best_highest + 1.0; -} - -bool huge_file::good() const { return _real_file->good(); } - -bool huge_file::eof() const { return _real_file->eof(); } - -outcome huge_file::move_to(double absolute_posn) -{ -#ifdef DEBUG_HUGE_FILE - FUNCDEF("move_to"); -#endif - double difference = absolute_posn - _file_pointer; - // calculate the size we want to offset. -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf("abs_pos=%.0f difference=%.0f old_filepoint=%.0f", - absolute_posn, difference, _file_pointer)); -#endif - // if we're at the same place, we don't have to do anything. - if (difference < 0.000001) { -#ifdef DEBUG_HUGE_FILE - LOG("difference was minimal, saying we're done."); -#endif - return OKAY; - } - while (absolute_value(difference) > 0.000001) { - double seek_size = minimum(double(byte_filer::file_size_limit() - 1), - absolute_value(difference)); - if (difference < 0) - seek_size *= -1.0; // flip sign of seek. -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" seeksize=%d", int(seek_size))); -#endif - bool seek_ret = _real_file->seek(int(seek_size), - byte_filer::FROM_CURRENT); - if (!seek_ret) { -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf("failed to seek %d from current", int(seek_size))); -#endif - return FAILURE; // seek failed somehow. - } - _file_pointer += seek_size; -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" now_filepoint=%.0f", _file_pointer)); -#endif - difference = absolute_posn - _file_pointer; -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" now_difference=%.0f", difference)); -#endif - } - return OKAY; -} - -outcome huge_file::seek(double new_position, byte_filer::origins origin) -{ -#ifdef DEBUG_HUGE_FILE - FUNCDEF("seek"); -#endif - if (origin == byte_filer::FROM_CURRENT) { - return move_to(_file_pointer + new_position); - } else if (origin == byte_filer::FROM_START) { - _file_pointer = 0; - if (!_real_file->seek(0, byte_filer::FROM_START)) - return FAILURE; - return move_to(new_position); - } else if (origin == byte_filer::FROM_END) { -#ifdef DEBUG_HUGE_FILE - LOG("into precarious FROM_END case."); -#endif - double file_len = length(); // could take a scary long time possibly. -#ifdef DEBUG_HUGE_FILE - LOG(a_sprintf(" FROM_END got len %.0f.", file_len)); -#endif - _file_pointer = file_len; - // it's safe, although not efficient, for us to call the length() - // method here. our current version of length() uses the byte_filer's - // seek method directly and only FROM_CURRENT and FROM_START from this - // class's seek method. - _real_file->seek(0, byte_filer::FROM_END); - return move_to(_file_pointer - new_position); - } - // unknown origin. - return BAD_INPUT; -} - -outcome huge_file::read(byte_array &to_fill, int desired_size, int &size_read) -{ -// FUNCDEF("read"); - size_read = 0; - int ret = _real_file->read(to_fill, desired_size); - if (ret < 0) - return FAILURE; // couldn't read the bytes. - _file_pointer += double(size_read); - size_read = ret; - return OKAY; -} - -outcome huge_file::write(const byte_array &to_write, int &size_written) -{ -// FUNCDEF("write"); - size_written = 0; - int ret = _real_file->write(to_write); - if (ret < 0) - return FAILURE; // couldn't write the bytes. - _file_pointer += double(size_written); - size_written = ret; - return OKAY; -} - -} //namespace. - diff --git a/core/library/filesystem/huge_file.h b/core/library/filesystem/huge_file.h deleted file mode 100644 index 69a10347..00000000 --- a/core/library/filesystem/huge_file.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef HUGE_FILE_CLASS -#define HUGE_FILE_CLASS - -/*****************************************************************************\ -* * -* Name : huge_file * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_filer.h" - -#include -#include -#include -#include - -namespace filesystem { - -//! Supports reading and writing to very large files, > 4 gigabytes. -/*! - The standard file I/O functions only handle files up to 4 gigabytes. This - class extends the range to essentially unlimited sizes, as long as the - operating system can accurately do relative seeks and can read/write to - files of the size needed. -*/ - -class huge_file -{ -public: - huge_file(const basis::astring &filename, const basis::astring &permissions); - //!< opens "filename" for access, where it presumably is a very large file. - /*!< see byte filer for a description of the permissions. - */ - virtual ~huge_file(); - - DEFINE_CLASS_NAME("huge_file"); - - enum outcomes { - OKAY = basis::common::OKAY, - FAILURE = basis::common::FAILURE, - ACCESS_DENIED = basis::common::ACCESS_DENIED, - BAD_INPUT = basis::common::BAD_INPUT - }; - - bool good() const; - //!< reports if the file was opened successfully. - - bool eof() const; - //!< reports when the file pointer has reached the end of the file. - - double length(); - //!< expensive operation accesses the file to find length. - - double file_pointer() const { return _file_pointer; } - //!< returns where we currently are in the file. - - basis::outcome seek(double new_position, - byte_filer::origins origin = byte_filer::FROM_CURRENT); - //!< move the file pointer to "new_position" if possible. - /*!< the relative seek is the easiest type of seek to accomplish with a - huge file. the other types are also supported, but take a bit more to - implement. */ - - basis::outcome move_to(double absolute_posn); - //!< simpler seek just goes from current location to "absolute_posn". - - basis::outcome read(basis::byte_array &to_fill, int desired_size, int &size_read); - //!< reads "desired_size" into "to_fill" if possible. - /*!< "size_read" reports how many bytes were actually read. */ - - basis::outcome write(const basis::byte_array &to_write, int &size_written); - //!< stores the array "to_write" into the file. - /*!< "size_written" reports how many bytes got written. */ - - bool truncate(); - //!< truncates the file after the current position. - - void flush(); - //!< forces any pending writes to actually be saved to the file. - -private: - byte_filer *_real_file; //!< supports us but is subject to 4g limit. - double _file_pointer; //!< position in the file if OS is tracking us. -}; - -} //namespace. - -#endif - diff --git a/core/library/filesystem/makefile b/core/library/filesystem/makefile deleted file mode 100644 index 746acf03..00000000 --- a/core/library/filesystem/makefile +++ /dev/null @@ -1,11 +0,0 @@ -include cpp/variables.def - -PROJECT = filesystem -TYPE = library -SOURCE = byte_filer.cpp directory.cpp directory_tree.cpp file_info.cpp \ - file_time.cpp filename.cpp filename_list.cpp filename_tree.cpp \ - heavy_file_ops.cpp huge_file.cpp -TARGETS = filesystem.lib - -include cpp/rules.def - diff --git a/core/library/loggers/combo_logger.cpp b/core/library/loggers/combo_logger.cpp deleted file mode 100644 index 591bde24..00000000 --- a/core/library/loggers/combo_logger.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/*****************************************************************************\ -* * -* Name : combo_logger -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "combo_logger.h" - -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif -#include -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace filesystem; -using namespace structures; -using namespace textual; - -namespace loggers { - -//const int REDUCE_FACTOR = 5; - // we whack this portion of the file every time we truncate. if it's set - // to 14, for example, then a 14th of the file is whacked every time whacking - // is needed. - -//const int MAXIMUM_BUFFER_SIZE = 140000; - // the maximum allowed chunk that can be copied from the old logfile - // to the current one. - -//int static_chaos() { return chaos().inclusive(0, 1280004); } - -combo_logger::combo_logger(const astring &filename, int limit, stream_choices target) -: file_logger(filename, limit), - console_logger(target) -{ -} - -void combo_logger::add_filter(int new_filter) -{ - file_logger::add_filter(new_filter); - console_logger::add_filter(new_filter); -} - -void combo_logger::remove_filter(int old_filter) -{ - file_logger::remove_filter(old_filter); - console_logger::remove_filter(old_filter); -} - -void combo_logger::clear_filters() -{ - file_logger::clear_filters(); - console_logger::clear_filters(); -} - -void combo_logger::eol(textual::parser_bits::line_ending to_set) -{ - file_logger::eol(to_set); - console_logger::eol(to_set); -} - -outcome combo_logger::log(const base_string &info, int filter) -{ - console_logger::log(info, filter); - return file_logger::log(info, filter); -} - -} //namespace. - diff --git a/core/library/loggers/combo_logger.h b/core/library/loggers/combo_logger.h deleted file mode 100644 index a593cf4e..00000000 --- a/core/library/loggers/combo_logger.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef COMBO_LOGGER_CLASS -#define COMBO_LOGGER_CLASS - -/*****************************************************************************\ -* * -* Name : combo_logger -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "file_logger.h" - -namespace loggers { - -//! combines a file_logger with a console logger, behaving like the 'tee' command. -/*! this will output the diagnostic info to both a file and to the console. this is useful -when one would like to see the output as its happening but also have a record for later. */ - -class combo_logger : public virtual file_logger, public virtual console_logger -{ -public: - combo_logger(const basis::astring &filename, - int limit = DEFAULT_LOG_FILE_SIZE, - stream_choices log_target = TO_STDOUT); - - virtual ~combo_logger() {} - - DEFINE_CLASS_NAME("combo_logger"); - - virtual basis::outcome log(const basis::base_string &info, int filter = basis::ALWAYS_PRINT); - - // overrides that enforce properties for both loggers. - virtual void add_filter(int new_filter); - virtual void remove_filter(int old_filter); - virtual void clear_filters(); - virtual void eol(textual::parser_bits::line_ending to_set); -}; - -////////////// - -//! a macro that retasks the program-wide logger as a combo_logger. -#define SETUP_COMBO_LOGGER { \ - basis::base_logger *old_log = program_wide_logger::set \ - (new loggers::combo_logger \ - (loggers::file_logger::log_file_for_app_name())); \ - WHACK(old_log); \ -} - -} //namespace. - -#endif - diff --git a/core/library/loggers/console_logger.cpp b/core/library/loggers/console_logger.cpp deleted file mode 100644 index aeb304b0..00000000 --- a/core/library/loggers/console_logger.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/*****************************************************************************\ -* * -* Name : console_logger * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "console_logger.h" -#include "logging_filters.h" - -#include - -using namespace basis; - -namespace loggers { - -console_logger::console_logger(stream_choices target) : c_target(target) -{} - -console_logger::~console_logger() {} - -outcome console_logger::log(const base_string &info, int filter) -{ - -if (filter) {} //temp ignored - - FILE *log_to = stdout; - if (c_target == TO_STDERR) log_to = stderr; - -//hmmm: temp simplified form during bootup of new hoople. -fprintf(log_to, "%s\n", (char *)info.observe()); - -/* -hmmm: need filter set support! - if (member(filter)) { -*/ - // format the output with %s to ensure we get all characters, rather - // than having some get interpreted if we used info as the format spec. -// fprintf(log_to, "%s", (char *)info.s()); - // send the EOL char if the style is appropriate for that. -// if (eol() != NO_ENDING) fprintf(log_to, "%s", get_ending().s()); - - - // write immediately to avoid lost output on crash. - fflush(log_to); - -/* -hmmm: need filter set support! - } -*/ - return common::OKAY; -} - -} //namespace. - - diff --git a/core/library/loggers/console_logger.h b/core/library/loggers/console_logger.h deleted file mode 100644 index c1984df8..00000000 --- a/core/library/loggers/console_logger.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef CONSOLE_LOGGER_CLASS -#define CONSOLE_LOGGER_CLASS - -/*****************************************************************************\ -* * -* Name : console_logger * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! A logger that sends to the console screen using the standard output device. -/*! - The object can optionally be used to log to the standard error device - instead. -*/ - -#include "standard_log_base.h" - -#include -#include -#include - -namespace loggers { - -class console_logger : public virtual standard_log_base -{ -public: - enum stream_choices { TO_STDOUT, TO_STDERR }; //!< where to send the logging. - - console_logger(stream_choices log_target = TO_STDOUT); - //!< if "standard_error" is true, than stderr is used instead of stdout. - - virtual ~console_logger(); - - DEFINE_CLASS_NAME("console_logger"); - - bool to_standard_output() const { return c_target == TO_STDOUT; } - //!< reports if the logger goes to standard output (default). - - bool to_standard_error() const { return c_target == TO_STDERR; } - //!< reports if the logger goes to standard error instead. - - void set_output(stream_choices target) { c_target = target; } - //!< enables the target of logging to be changed after construction. - - virtual basis::outcome log(const basis::base_string &info, int filter); - //!< sends the string "info" to the standard output device. - /*!< if the "filter" is not in the current filter set, then the - information will be dropped. otherwise the information is displayed, - where appropriate. for some environments, this will essentially throw - it away. for example, in ms-windows, a windowed program will only save - standard out if one redirects standard output to a file, whereas a - console mode program will output the text to its parent prompt. */ - -private: - stream_choices c_target; //!< records whether stdout or stderr is used. -}; - -////////////// - -//!< a macro that retasks the program-wide logger as a console_logger. -#define SETUP_CONSOLE_LOGGER { \ - loggers::standard_log_base *old_log = loggers::program_wide_logger::set \ - (new loggers::console_logger); \ - /* assumes we are good to entirely remove the old logger. */ \ - WHACK(old_log); \ -} - -} // namespace. - -#endif - diff --git a/core/library/loggers/critical_events.cpp b/core/library/loggers/critical_events.cpp deleted file mode 100644 index ebb60bb8..00000000 --- a/core/library/loggers/critical_events.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/*****************************************************************************\ -* * -* Name : critical_events * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "critical_events.h" -#include "program_wide_logger.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace structures; -using namespace configuration; -using namespace textual; -using namespace timely; - -const int MESSAGE_SPACE_PROVIDED = 4096; - // the strings should not be larger than this for diagnostic / error messages. - -namespace loggers { - -SAFE_STATIC(astring, critical_events::hidden_critical_events_dir, ) - // define the function that holds the directory string. - -astring default_critical_location() -{ - // ensure that the critical events function logs to the appropriate place. - return application_configuration::get_logging_directory(); -} - -SAFE_STATIC(mutex, __critical_event_dir_lock, ) - -basis::un_int critical_events::system_error() -{ -#if defined(__UNIX__) - return errno; -#elif defined(__WIN32__) - return GetLastError(); -#else - #pragma error("hmmm: no code for error number for this operating system") - return 0; -#endif -} - -astring critical_events::system_error_text(basis::un_int to_name) -{ -#if defined(__UNIX__) - return strerror(to_name); -#elif defined(__WIN32__) - char error_text[1000]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NIL, to_name, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)error_text, - sizeof(error_text) - 1, NIL); - astring to_return = error_text; - // trim off the ridiculous carriage return they add. - while ( (to_return[to_return.end()] == '\r') - || (to_return[to_return.end()] == '\n') ) - to_return.zap(to_return.end(), to_return.end()); - return to_return; -#else - #pragma error("hmmm: no code for error text for this operating system") - return ""; -#endif -} - -astring critical_events::critical_events_directory() -{ - static bool initted = false; - if (!initted) { - auto_synchronizer l(__critical_event_dir_lock()); - if (!initted) { - set_critical_events_directory(default_critical_location()); - initted = true; - } - } - return hidden_critical_events_dir(); -} - -void critical_events::set_critical_events_directory(const astring &directory) -{ hidden_critical_events_dir() = directory; } - -void critical_events::write_to_critical_events(const char *to_write) -{ - astring filename = critical_events_directory(); - filename += "/runtime_issues.log"; - FILE *errfile = fopen(filename.s(), "ab"); - if (errfile) { - astring app_name = application_configuration::application_name(); - int indy = app_name.find('/', app_name.end(), true); - if (non_negative(indy)) app_name.zap(0, indy); - indy = app_name.find('\\', app_name.end(), true); - if (non_negative(indy)) app_name.zap(0, indy); - fprintf(errfile, "%s [%s]:%s", time_stamp::notarize(true).s(), - app_name.s(), parser_bits::platform_eol_to_chars()); - fprintf(errfile, "%s%s", to_write, parser_bits::platform_eol_to_chars()); - fclose(errfile); - } -} - -void critical_events::write_to_console(const char *guards_message_space) -{ fprintf(stderr, "%s", (char *)guards_message_space); fflush(stderr); } - -void critical_events::alert_message(const char *info, const char *title) -{ - astring to_print; - if (strlen(title)) { - const char border = '='; - to_print += astring(border, int(strlen(title)) + 4); - to_print += parser_bits::platform_eol_to_chars(); - to_print += border; - to_print += ' '; - to_print += title; - to_print += ' '; - to_print += border; - to_print += parser_bits::platform_eol_to_chars(); - to_print += astring(border, int(strlen(title)) + 4); - to_print += parser_bits::platform_eol_to_chars(); - } - - to_print += info; - program_wide_logger::get().log(to_print, ALWAYS_PRINT); - fflush(NIL); // flush all output streams. -} - -void critical_events::alert_message(const astring &info) { alert_message(info.s()); } - -void critical_events::alert_message(const astring &info, const astring &title) -{ alert_message(info.s(), title.s()); } - -void critical_events::make_error_message(const char *file, int line, - const char *error_class, const char *error_function, - const char *info, char *guards_message_space) -{ - strcpy(guards_message_space, "\nProblem reported for \""); -//hmmm: only copy N chars of each into the space. -// say 40 for class/function each, then the space - consumed for the -// info. get strlen for real on the class and function name to know -// actual size for that computation. - strcat(guards_message_space, error_class); - strcat(guards_message_space, "::"); - strcat(guards_message_space, error_function); - strcat(guards_message_space, "\"\n(invoked at line "); - char line_num[20]; - sprintf(line_num, "%d", line); - strcat(guards_message_space, line_num); - strcat(guards_message_space, " in "); - strcat(guards_message_space, file); - strcat(guards_message_space, " at "); - strcat(guards_message_space, time_stamp::notarize(false).s()); - strcat(guards_message_space, ")\n"); - strcat(guards_message_space, info); - strcat(guards_message_space, "\n\n\n"); -} - -void critical_events::FL_deadly_error(const char *file, int line, const char *error_class, - const char *error_function, const char *info) -{ - FL_continuable_error(file, line, error_class, error_function, info, - "Deadly Error Information"); - CAUSE_BREAKPOINT; - throw "deadly_error"; - // abort() is not as good an approach as throwing an exception. aborts are - // harder to track with some compilers, but all should be able to trap an - // exception. -} - -void critical_events::FL_deadly_error(const astring &file, int line, - const astring &error_class, const astring &error_function, - const astring &info) -{ - FL_deadly_error(file.s(), line, error_class.s(), error_function.s(), - info.s()); -} - -void critical_events::FL_continuable_error_real(const char *file, int line, - const char *error_class, const char *error_function, const char *info, - const char *title) -{ - char guards_message_space[MESSAGE_SPACE_PROVIDED]; - // this routine could still fail, if the call stack is already jammed - // against its barrier. but if that's the case, even the simple function - // call might fail. in any case, we cannot deal with a stack overflow - // type error using this function. but we would rather deal with that - // than make the space static since that would cause corruption when - // two threads wrote errors at the same time. - make_error_message(file, line, error_class, error_function, info, - guards_message_space); - alert_message(guards_message_space, title); -} - -void critical_events::FL_continuable_error(const char *file, int line, - const char *error_class, const char *error_function, const char *info, - const char *title) -{ - FL_continuable_error_real(file, line, error_class, error_function, info, title); -} - -void critical_events::FL_continuable_error(const astring &file, int line, - const astring &error_class, const astring &error_function, - const astring &info, const astring &title) -{ - FL_continuable_error_real(file.s(), line, error_class.s(), - error_function.s(), info.s(), title.s()); -} - -void critical_events::FL_non_continuable_error(const char *file, int line, - const char *error_class, const char *error_function, const char *info, - const char *title) -{ - FL_continuable_error_real(file, line, error_class, error_function, info, - title); - exit(EXIT_FAILURE); // let the outside world know that there was a problem. -} - -void critical_events::FL_non_continuable_error(const astring &file, int line, - const astring &error_class, const astring &error_function, - const astring &info, const astring &title) -{ - FL_continuable_error_real(file.s(), line, error_class.s(), - error_function.s(), info.s(), title.s()); - exit(EXIT_FAILURE); // let the outside world know that there was a problem. -} - -void critical_events::FL_console_error(const char *file, int line, const char *error_class, - const char *error_function, const char *info) -{ - char guards_message_space[MESSAGE_SPACE_PROVIDED]; - make_error_message(file, line, error_class, error_function, info, - guards_message_space); - write_to_console(guards_message_space); -} - -void critical_events::FL_out_of_memory_now(const char *file, int line, - const char *the_class_name, const char *the_func) -{ - FL_non_continuable_error(file, line, the_class_name, the_func, - "Program stopped due to memory allocation failure.", - "Out of Memory Now"); -} - -void critical_events::implement_bounds_halt(const char *the_class_name, const char *the_func, - const char *value, const char *low, const char *high, - const char *error_addition) -{ - char message[400]; - strcpy(message, "bounds error caught"); - strcat(message, error_addition); - strcat(message, ":\r\n"); - strcat(message, value); - strcat(message, " is not between "); - strcat(message, low); - strcat(message, " and "); - strcat(message, high); -#ifdef ERRORS_ARE_FATAL - deadly_error(the_class_name, the_func, message); -#else - continuable_error(the_class_name, the_func, message); -#endif -} - -} //namespace. - diff --git a/core/library/loggers/critical_events.h b/core/library/loggers/critical_events.h deleted file mode 100644 index f814de50..00000000 --- a/core/library/loggers/critical_events.h +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef CRITICAL_EVENTS_GROUP -#define CRITICAL_EVENTS_GROUP - -/*****************************************************************************\ -* * -* Name : critical_events * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace loggers { - -//! This macro wraps the notion of stopping in the debugger. -#ifdef __UNIX__ - #define CAUSE_BREAKPOINT -//hmmm: need a unix equivalent for this. supporting gcc might be enough. -#elif defined(__WIN32__) - #ifdef __MINGW32__ - extern "C" { -// #include -//for debugbreak. does this cause other problems? - } - #define CAUSE_BREAKPOINT -// #define CAUSE_BREAKPOINT __debugbreak() - #else - #define CAUSE_BREAKPOINT __debugbreak() - #endif -#endif - -//! Tests the value "check" to ensure that it's not zero. -/*! This can be used instead of an ASSERT macro to check conditions in -builds with ERRORS_ARE_FATAL turned on. This macro is orthogonal to the -build being built with debugging or release features. */ -#ifdef ERRORS_ARE_FATAL - #define REQUIRE(check) if (!check) CAUSE_BREAKPOINT; -#else - #define REQUIRE(check) -#endif - -// now more dangerous or potent guards... - -//! an extra piece of information used, if available, in bounds_halt below. -//#define BH_ERROR_ADDITION ((basis::astring(" in ") + _global_argv[0]).s()) - -//! Verifies that "value" is between "low" and "high", inclusive. -/*! "Value" must be an object for which greater than and less than are defined. -The static_class_name() method and func definition are used to tag the -complaint that is emitted when problems are detected. Note that if -CATCH_ERRORS is defined, then the program is _halted_ if the value is out -of bounds. Otherwise, the "to_return" value is returned. */ -//#ifdef CATCH_ERRORS -// #define bounds_halt(value, low, high, to_return) { -// if (((value) < (low)) || ((value) > (high))) { -// critical_events::implement_bounds_halt(static_class_name(), func, #value, #low, -// #high, BH_ERROR_ADDITION); -// return to_return; -// } -// } -//#else -// #define bounds_halt(a, b, c, d) bounds_return(a, b, c, d) -//#endif - -////////////// - -//! Provide some macros that will automatically add the file and line number. -/*! These use the functions below to report different types of error -situations and in some cases, exit the program. */ -#define non_continuable_error(c, f, i) \ - critical_events::FL_non_continuable_error(__FILE__, __LINE__, c, f, i, \ - "A Non-Continuable Runtime Problem Has Occurred") -#define continuable_error(c, f, i) \ - critical_events::FL_continuable_error(__FILE__, __LINE__, c, f, i, \ - "Runtime Problem Information") -#define console_error(c, f, i) \ - critical_events::FL_console_error(__FILE__, __LINE__, c, f, i) -#define deadly_error(c, f, i) \ - critical_events::FL_deadly_error(__FILE__, __LINE__, c, f, i) -#define out_of_memory_now(c, f) \ - critical_events::FL_out_of_memory_now(__FILE__, __LINE__, c, f) - -////////////// - -//! Provides a means of logging events for runtime problems. - -class critical_events : public virtual basis::root_object -{ -public: - virtual ~critical_events() {} - - // super handy system inter-operation functions... - - static basis::un_int system_error(); - //!< gets the most recent system error reported on this thread. - - static basis::astring system_error_text(basis::un_int error_to_show); - //!< returns the OS's string form of the "error_to_show". - /*!< this often comes from the value reported by system_error(). */ - - // some noisemaking methods... - - //! Prints out a message to the standard error file stream. - static void write_to_console(const char *message); - - //! shows the message in "info", with an optional "title" on the message. - /*! the message is sent to the program wide logger, if one is expected to - exist in this program. */ - static void alert_message(const char *info, const char *title = "Alert Message"); - static void alert_message(const basis::astring &info); // uses default title. - static void alert_message(const basis::astring &info, const basis::astring &title); // use "title". - - static void write_to_critical_events(const char *message); - //!< sends the "message" to the critical events log file. - /*!< Prints out a message to the specially named file that captures all - serious events written to error logging functions. If you use the - functions in this file, you are likely already writing to this log. */ - - static void set_critical_events_directory(const basis::astring &directory); - //!< sets the internal location where the critical events will be logged. - /*!< this is postponed to a higher level, although the default - will work too. */ - - static basis::astring critical_events_directory(); - //!< returns the current location where critical events are written. - - // this section implements the error macros. - - //! Prints out an error message and then exits the program. - /*! The parameters describe the location where the error was detected. This - call should only be used in test programs or where absolutely necessary - because it causes an almost immediate exit from the program! deadly_error - should be reserved for when the program absolutely has to exit right then, - because this function will actually enforce a crash / abort / break into - debugger action rather than just calling exit(). */ - static void FL_deadly_error(const char *file, int line, const char *classname, - const char *function_name, const char *info); - //! A version that takes strings instead of char pointers. - static void FL_deadly_error(const basis::astring &file, int line, - const basis::astring &classname, const basis::astring &function_name, - const basis::astring &info); - - //! Describes an error like deadly_error, but does not exit the program. - static void FL_continuable_error(const char *file, int line, - const char *classname, const char *function_name, const char *info, - const char *title); - - //! A version using astring instead of char pointers. - static void FL_continuable_error(const basis::astring &file, int line, - const basis::astring &classname, const basis::astring &function_name, - const basis::astring &info, const basis::astring &title); - - //! Shows the same information as continuable_error, but causes an exit. - /*! This is a friendlier program exit than deadly_error. This version can - be used when the program must stop, possibly because of a precondition failure - or a problem in input data or basically whatever. it is not intended to - abort or break into the debugger, but to simply exit(). */ - static void FL_non_continuable_error(const char *file, int line, - const char *classname, const char *function_name, const char *info, - const char *title); - - //! A version using astring instead of char pointers. - static void FL_non_continuable_error(const basis::astring &file, int line, - const basis::astring &classname, const basis::astring &function_name, - const basis::astring &info, const basis::astring &title); - - //! Causes the program to exit due to a memory allocation failure. - static void FL_out_of_memory_now(const char *file, int line, - const char *classname, const char *function_name); - - //! Prints out an error message to the standard error file stream. - static void FL_console_error(const char *file, int line, const char *error_class, - const char *error_function, const char *info); - - //! Used to build our particular type of error message. - /*! It writes an error message into "guards_message_space" using our stylized - object::method formatting. */ - static void make_error_message(const char *file, int line, - const char *error_class, const char *error_function, - const char *info, char *guards_message_space); - - //! Provides the real implementation of bounds_halt to save code space. - static void implement_bounds_halt(const char *the_class_name, const char *func, - const char *value, const char *low, const char *high, - const char *error_addition); - -private: - static basis::astring &hidden_critical_events_dir(); - - static void FL_continuable_error_real(const char *file, int line, - const char *error_class, const char *error_function, const char *info, - const char *title); -}; - -} //namespace. - -#endif - diff --git a/core/library/loggers/definitions_wx.h b/core/library/loggers/definitions_wx.h deleted file mode 100644 index 9a4c29e7..00000000 --- a/core/library/loggers/definitions_wx.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef WX_DEFINITIONS_GROUP -#define WX_DEFINITIONS_GROUP - -/*****************************************************************************\ -* * -* Name : definitions for wx * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Some macros that support the use of WX widgets. * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -#include - -namespace loggers { - -#ifdef wxUSE_UNICODE - //! converts to wx's UTF-16 string format. - #define to_unicode_wx(s) ((const wxChar *)(const flexichar *)transcode_to_utf16(s)) - //! converts a wx UTF-16 string to UTF-8. - #define from_unicode_wx(s) ((const char *)(const UTF8 *)transcode_to_utf8(s)) -#else - // turn off the unicode macro to ensure nothing gets confused. - #undef UNICODE - // placeholder macros try not to do much. - #define to_unicode_wx(s) ((const wxChar *)null_transcoder(s, false)) - #define from_unicode_wx(s) ((const char *)null_transcoder((const wxChar *)s, false)) -#endif - -} //namespace. - -#endif - diff --git a/core/library/loggers/eol_aware.h b/core/library/loggers/eol_aware.h deleted file mode 100644 index 9e6280f8..00000000 --- a/core/library/loggers/eol_aware.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef EOL_AWARE_CLASS -#define EOL_AWARE_CLASS - -/*****************************************************************************\ -* * -* Name : eol_aware -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace loggers { - -//! Provides an abstract base for logging mechanisms. -/*! - This assists greatly in generating diagnostic entries polymorphically and in - enabling different logging mechanisms to be plugged in easily. Note that all - of the functions are defined virtually which enables overriding pretty much - all of the base functionality. Use this wisely, if ever. -*/ - -class eol_aware : public virtual basis::root_object -{ -public: - virtual textual::parser_bits::line_ending eol() { return c_ending; } - //!< observes how line endings are to be printed. - - virtual void eol(textual::parser_bits::line_ending to_set) { c_ending = to_set; } - //!< modifies how line endings are to be printed. - - virtual basis::astring get_ending() { return textual::parser_bits::eol_to_chars(c_ending); } - //!< returns a string for the current ending. - - virtual void get_ending(basis::astring &to_end) { to_end = textual::parser_bits::eol_to_chars(c_ending); } - //!< appends a string for the current ending to "to_end". - -private: - textual::parser_bits::line_ending c_ending; //!< the current printing style. -}; - -} //namespace. - -#endif - diff --git a/core/library/loggers/file_logger.cpp b/core/library/loggers/file_logger.cpp deleted file mode 100644 index a18caa14..00000000 --- a/core/library/loggers/file_logger.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/*****************************************************************************\ -* * -* Name : file_logger * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "critical_events.h" -#include "file_logger.h" -#include "logging_filters.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif -#include -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace mathematics; -using namespace structures; -using namespace textual; - -namespace loggers { - -const int REDUCE_FACTOR = 5; - // we whack this portion of the file every time we truncate. if it's set - // to 14, for example, then a 14th of the file is whacked every time whacking - // is needed. - -const int MAXIMUM_BUFFER_SIZE = 140000; - // the maximum allowed chunk that can be copied from the old logfile - // to the current one. - -int static_chaos() { - static chaos __hidden_chaos; - return __hidden_chaos.inclusive(0, 1280004); -} - -file_logger::file_logger() -: _filename(new astring()), - _file_limit(DEFAULT_LOG_FILE_SIZE), - _outfile(NIL), - _flock(new mutex) -{ - name(""); -} - -file_logger::file_logger(const astring &initial_filename, int limit) -: _filename(new astring()), - _file_limit(limit), - _outfile(NIL), - _flock(new mutex) -{ - name(initial_filename); - // we don't open the file right away because we don't know they'll ever - // use the thing. -} - -file_logger::~file_logger() -{ - close_file(); - WHACK(_filename); - WHACK(_flock); -} - -basis::astring file_logger::log_file_for_app_name() -{ - filename prog = application_configuration::application_name(); - return application_configuration::make_logfile_name(prog.rootname() + ".log"); -} - -bool file_logger::reopen() -{ - auto_synchronizer l(*_flock); - name(*_filename); - return open_file(); -} - -void file_logger::close_file() -{ - auto_synchronizer l(*_flock); - if (_outfile) _outfile->flush(); - // dump anything that hasn't gone out yet. - WHACK(_outfile); -} - -void file_logger::name(const astring &new_name) -{ - auto_synchronizer l(*_flock); - close_file(); - *_filename = new_name; -} - -int file_logger::size_reduction() const -{ - auto_synchronizer l(*_flock); - return int(_file_limit / REDUCE_FACTOR); -} - -bool file_logger::good() const -{ - auto_synchronizer l(*_flock); - if (!_outfile && !_file_limit) return true; - if (!_outfile) return false; - return _outfile->good(); -} - -astring file_logger::name() const -{ - auto_synchronizer l(*_flock); - return *_filename; -} - -void file_logger::flush() -{ - auto_synchronizer l(*_flock); - if (!_outfile) open_file(); - if (_outfile) _outfile->flush(); -} - -bool file_logger::open_file() -{ - auto_synchronizer l(*_flock); - close_file(); // close any existing log file. - - if (!_file_limit) { - // if there's a limit of zero, we'll never open the file. - return true; - } - - // make sure we've got a name. - if (!*_filename) { - // if the name is empty, they don't want to save to a normal log file. - _outfile = new byte_filer; - return true; - } - - // canonicalize the name so that we use the same tag for synchronization. - // this might still fail if there are some jokers using different relative - // paths to the file. but if it's an absolute name, it should work. - for (int i = 0; i < _filename->length(); i++) - if ((*_filename)[i] == '\\') (*_filename)[i] = '/'; - - // make sure the directory containing the log file exists, if we can. - filename temp_file(*_filename); - filename temp_dir(temp_file.dirname()); - if (!temp_dir.good() || !temp_dir.is_directory()) { - directory::recursive_create(temp_dir); - } - - // if this opening doesn't work, then we just can't log. - _outfile = new byte_filer(*_filename, "a+b"); - return _outfile->good(); -} - -outcome file_logger::log(const base_string &to_show, int filter) -{ - if (!_file_limit) return common::OKAY; - - size_t current_size = 0; - { - auto_synchronizer l(*_flock); - if (!member(filter)) return common::OKAY; - if (!_outfile) open_file(); - if (!_outfile) return common::BAD_INPUT; // file opening failed. - if (!_outfile->good()) return common::BAD_INPUT; - // there is no log file currently. - - // dump the string out. - if (to_show.length()) - _outfile->write((abyte *)to_show.observe(), to_show.length()); -//hmmm: need eol feature again. -// if (eol() != NO_ENDING) { -// astring end = get_ending(); -astring end = parser_bits::platform_eol_to_chars(); - _outfile->write((abyte *)end.s(), end.length()); -// } - current_size = _outfile->tell(); - flush(); - } - - // check if we need to truncate yet. - if (current_size > _file_limit) truncate(_file_limit - size_reduction()); - return common::OKAY; -} - -outcome file_logger::log_bytes(const byte_array &to_log, int filter) -{ - if (!_file_limit) return common::OKAY; - - size_t current_size = 0; - { - auto_synchronizer l(*_flock); - if (!member(filter)) return common::OKAY; - if (!_outfile) open_file(); - if (!_outfile) return common::BAD_INPUT; // file opening failed. - if (!_outfile->good()) return common::BAD_INPUT; - // there is no log file currently. - - // dump the contents out. - if (to_log.length()) - _outfile->write(to_log.observe(), to_log.length()); - current_size = _outfile->tell(); - flush(); - } - - // check if we need to truncate yet. - if (current_size > _file_limit) - truncate(_file_limit - size_reduction()); - return common::OKAY; -} - -outcome file_logger::format_bytes(const byte_array &to_log, int filter) -{ - if (!_file_limit) return common::OKAY; - - { - auto_synchronizer l(*_flock); - if (!member(filter)) return common::OKAY; - if (!_outfile) open_file(); - if (!_outfile) return common::BAD_INPUT; // file opening failed. - if (!_outfile->good()) return common::BAD_INPUT; - // there is no log file currently. - } - - // dump the contents out. - if (to_log.length()) { - astring dumped_form; - byte_formatter::text_dump(dumped_form, to_log); - log(dumped_form); - } - - // check if we need to truncate yet. -// int current_size = _outfile->tell(); -// flush(); -// if (current_size > _file_limit) truncate(_file_limit - size_reduction()); - - return common::OKAY; -} - -//hmmm: should move the truncation functionality into a function on -// the file object. - -void file_logger::truncate(size_t new_size) -{ - auto_synchronizer l(*_flock); - if (!_outfile) open_file(); - if (!_outfile) return; // file opening failed. - if (!_outfile->good()) return; - // there is no log file currently. - - size_t current_size = 0; - -/// // our synchronization scheme allows us to use this inter-application -/// // lock; the logger's own lock is always acquired first. no one else can -/// // grab the "file_lock", so no deadlocks. -/// -/// rendezvous file_lock(*_filename + "_trunclock"); -/// if (!file_lock.healthy()) { -/// critical_events::write_to_critical_events((astring("could not create " -/// "lock for ") + *_filename).s()); -/// return; -/// } -/// // waiting forever until the file lock succeeds. as long as there are -/// // no deadlocks permitted, then this shouldn't be too dangerous... -/// bool got_lock = file_lock.lock(rendezvous::ENDLESS_WAIT); -/// if (!got_lock) { -/// critical_events::write_to_critical_events((astring("could not acquire " -/// "lock for ") + *_filename).s()); -/// return; -/// } - - // make sure we weren't second in line to clean the file. if someone else - // already did this, we don't need to do it again. - _outfile->seek(0, byte_filer::FROM_END); - current_size = _outfile->tell(); - if (current_size <= new_size) { - // release the lock and leave since we don't need to change the file. -/// file_lock.unlock(); - return; - } - // create a bogus temporary name. - astring new_file(astring::SPRINTF, "%s.tmp.%d", name().s(), - static_chaos()); - - // unlink the temp file, if it exists. - unlink(new_file.s()); - - // grab the current size before we close our file. - current_size = _outfile->tell(); - - // redo the main stream for reading also. - WHACK(_outfile); - _outfile = new byte_filer(*_filename, "rb"); - - // open the temp file as blank for writing. - byte_filer *hold_stream = new byte_filer(new_file, "w+b"); - - int start_of_keep = int(current_size - new_size); - - // position the old file where it will be about the right size. - _outfile->seek(start_of_keep, byte_filer::FROM_START); - - astring buff(' ', MAXIMUM_BUFFER_SIZE + 1); - - // we only read as long as the file end isn't hit and we're not past the - // original end of the file. if the file got bigger during the truncation, - // that's definitely not our doing and should not be coddled. we've seen - // a situation where we never thought we'd hit the end of the file yet before - // adding this size check. - size_t bytes_written = 0; // how many bytes have gone out already. -//hmmm: loop could be extracted to some kind of dump from file one into file -// two operation that starts at a particular address and has a particular -// size or range. - while (!_outfile->eof() && (bytes_written <= new_size)) { - // grab a line from the input file. - buff[0] = '\0'; // set it to be an empty string. - int bytes_read = _outfile->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE); - if (!bytes_read) - break; - bytes_written += bytes_read; - // write the line and a CR to the output file. - if (!_outfile->eof() || bytes_read) - hold_stream->write((abyte *)buff.s(), bytes_read); - } - WHACK(_outfile); - _outfile = new byte_filer(*_filename, "w+b"); - - // we think the new stream is ready for writing. - size_t hold_size = hold_stream->tell(); - // get the current length of the clipped chunk. - bytes_written = 0; // reset our counter. - - // jump back to the beginning of the temp file. - hold_stream->seek(0, byte_filer::FROM_START); - while (!hold_stream->eof() && (bytes_written <= hold_size) ) { - // scoot all the old data back into our file. - int bytes_read = hold_stream->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE); - if (!bytes_read) - break; - // something funky happened; we shouldn't be at the end of the file yet. - bytes_written += bytes_read; - if (!hold_stream->eof() || bytes_read) - _outfile->write((abyte *)buff.s(), bytes_read); - } - WHACK(hold_stream); - unlink(new_file.s()); // trash the temp file. -/// file_lock.unlock(); // repeal the process-wide lock. - name(*_filename); // re-open the regular file with append semantics. -} - -} //namespace. - diff --git a/core/library/loggers/file_logger.h b/core/library/loggers/file_logger.h deleted file mode 100644 index 3fc0319b..00000000 --- a/core/library/loggers/file_logger.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef FILE_LOGGER_CLASS -#define FILE_LOGGER_CLASS - -/*****************************************************************************\ -* * -* Name : file_logger * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//! Enables the printing of information to a log file. -/*! - The information can be conditionally printed using the filter support. - The log file will automatically be truncated when it passes the size limit. -*/ - -#include "console_logger.h" -#include "eol_aware.h" -#include "filter_set.h" - -#include -#include -#include -#include -#include -#include - -namespace loggers { - -class file_logger : public virtual standard_log_base -{ -public: - file_logger(); - //!< creates a logger without a log file and with the default size limit. - /*!< the log file name can be changed using filename(). */ - - file_logger(const basis::astring &filename, int limit = DEFAULT_LOG_FILE_SIZE); - //!< constructs a logger using the "filename" for output. - /*!< there will be no logging if the "filename" is empty. the "limit" - specifies how large the log file can be (in bytes). */ - - virtual ~file_logger(); - - DEFINE_CLASS_NAME("file_logger"); - - enum limits { - //! this just defines the default for the log file size. - DEFAULT_LOG_FILE_SIZE = 0x10F00D - }; - - bool good() const; - //!< returns true if the logger appears correctly hooked up to a file. - /*!< note that we don't open the file when file_logger is constructed; - it is only opened once the first logging is attempted. */ - - bool reopen(); - //!< closes the current file and attempts to reopen it. - /*!< this is handy if the original opening of the file failed. */ - - basis::outcome log(const basis::base_string &info, int filter = basis::ALWAYS_PRINT); - //!< writes information to the log file (if the filename is valid). - /*!< the "filter" value is checked to see if it is in the current set - of allowed filters. a value of zero is always printed. if the filename() - has not been set, then the information is lost. */ - - basis::outcome log_bytes(const basis::byte_array &to_log, int filter = basis::ALWAYS_PRINT); - //!< sends a stream of bytes "to_log" without interpretation into the log. - /*!< if the "filter" is not enabled, then the info is just tossed out. */ - - basis::outcome format_bytes(const basis::byte_array &to_log, int filter = basis::ALWAYS_PRINT); - //!< fancifully formats a stream of bytes "to_log" and sends them into log. - - basis::astring name() const; - //!< observes the filename where logged information is written. - void name(const basis::astring &new_name); - //!< modifies the filename where logged information will be written. - /*!< if "new_name" is blank, then the logged information will not - be saved. */ - - int limit() const { return int(_file_limit); } - //!< observes the allowable size of the log file. - void limit(int new_limit) { _file_limit = new_limit; } - //!< modifies the allowable size of the log file. - - void flush(); - //!< causes any pending writes to be sent to the output file. - - void truncate(size_t new_size); - //!< chops the file to ensure it doesn't go much over the file size limit. - /*!< this can be used externally also, but be careful with it. */ - - //! returns a log file name for file_logger based on the program name. - /*! for a program named myapp.exe, this will be in the form: - {logging_dir}/myapp.log - */ - static basis::astring log_file_for_app_name(); - -private: - basis::astring *_filename; //!< debugging output file. - size_t _file_limit; //!< maximum length of file before truncation. - filesystem::byte_filer *_outfile; //!< the object that points at our output file. - basis::mutex *_flock; //!< protects the file and other parameters. - - int size_reduction() const; - //!< returns the size of the chunk to truncate from the file. - /*!< this is a percentage of the maximum size allowed. */ - - bool open_file(); - //!< if the opening of the file is successful, then true is returned. - /*!< also, the _outfile member variable is non-zero. */ - - void close_file(); - //!< shuts down the file, if any, we had opened for logging. - - // unavailable. - file_logger(const file_logger &); - file_logger &operator =(const file_logger &); -}; - -////////////// - -//! a macro that retasks the program-wide logger as a file_logger. -#define SETUP_FILE_LOGGER { \ - loggers::standard_log_base *old_log = loggers::program_wide_logger::set \ - (new loggers::file_logger(loggers::file_logger::log_file_for_app_name())); \ - WHACK(old_log); \ -} - -} //namespace. - -#endif - diff --git a/core/library/loggers/filter_set.h b/core/library/loggers/filter_set.h deleted file mode 100644 index 19b0baea..00000000 --- a/core/library/loggers/filter_set.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef FILTER_SET_CLASS -#define FILTER_SET_CLASS - -/*****************************************************************************\ -* * -* Name : filter_set -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -namespace loggers { - -//! A simple object that wraps a templated set of ints. -class filter_set : public structures::set, public virtual basis::root_object -{ -public: - filter_set() {} - //!< Constructs an empty set of filters. - - virtual ~filter_set() {} - - filter_set(const structures::set &to_copy) : structures::set(to_copy) {} - //!< Constructs a copy of the "to_copy" array. - - DEFINE_CLASS_NAME("filter_set"); - - //! Adds a member to the filter set. - /*! The filter set is used to check all extended filter values passed to - log and print. if the special filters of ALWAYS_PRINT or NEVER_PRINT are - added, then either everything will be logged or nothing will be. */ - virtual void add_filter(int new_filter) { - basis::auto_synchronizer l(c_lock); - add(new_filter); - } - - //! Removes a member from the filter set. - virtual void remove_filter(int old_filter) { - basis::auto_synchronizer l(c_lock); - remove(old_filter); - } - - //! Returns true if the "filter_to_check" is a member of the filter set. - /*! If "filter_to_check" is ALWAYS_PRINT, this always returns true. If - the value is NEVER_PRINT, false is always returned. */ - virtual bool member(int filter_to_check) { - if (filter_to_check == basis::ALWAYS_PRINT) return true; - if (filter_to_check == basis::NEVER_PRINT) return false; - return structures::set::member(filter_to_check); - } - - //! Resets the filter set to be empty. - virtual void clear_filters() { - basis::auto_synchronizer l(c_lock); - clear(); - } - -private: - basis::mutex c_lock; -}; - -} // namespace. - -#endif - diff --git a/core/library/loggers/logging_filters.h b/core/library/loggers/logging_filters.h deleted file mode 100644 index 43ae2b49..00000000 --- a/core/library/loggers/logging_filters.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef LOGGING_FILTERS_GROUP -#define LOGGING_FILTERS_GROUP - -////////////// -// Name : logging_filters -// Author : Chris Koeritz -////////////// -// Copyright (c) 2010-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include - -namespace loggers { - - enum logging_filters { - // synonyms for filters that are important enough to always show. - FILT_FATAL = basis::ALWAYS_PRINT, - FILT_ERROR = basis::ALWAYS_PRINT, - // the first really selectable filters follow... one might notice a - // small similarity to levels available with log4j. - DEFINE_FILTER(FILT_WARNING, 1, "Important or unusual condition occurred in the runtime."), - DEFINE_FILTER(FILT_INFO, 2, "Information from normal runtime activities."), - DEFINE_FILTER(FILT_DEBUG, 3, "Noisy debugging information from objects."), - // occasionally useful filters for locally defined categories. - DEFINE_FILTER(FILT_UNUSUAL, 4, "Unusual but not necessarily erroneous events."), - DEFINE_FILTER(FILT_NETWORK, 5, "Network activity and events."), - DEFINE_FILTER(FILT_DATABASE, 6, "Data storage activities or warnings.") - }; - -} //namespace. - -#endif - diff --git a/core/library/loggers/logging_macros.h b/core/library/loggers/logging_macros.h deleted file mode 100644 index 31547a41..00000000 --- a/core/library/loggers/logging_macros.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef LOGGING_MACROS_GROUP -#define LOGGING_MACROS_GROUP - -/*****************************************************************************\ -* * -* Name : logging macros * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -/*! @file logging_macros.h - these macros can assist in logging. they rely on the base_logger class and the program-wide - logger class for logging services. note that it is often convenient to customize one or more - of these to yield a simpler macro name per project, such as PRINT or LOG. -*/ - -#include -#include -#include - -///hmmm: very temporary until the call stack tracking is available again. -#define update_current_stack_frame_line_number(x) - -//! Logs a string "to_log" on "the_logger" using the "filter". -/*! The filter is checked before the string is allowed to come into -existence, which saves allocations when the item would never be printed -out anyway. */ -#define FILTER_LOG(the_logger, to_log, filter) { \ - if (the_logger.member(filter)) { \ - the_logger.log(to_log, filter); \ - } \ -} - -//! Logs a string at the emergency level, meaning that it always gets logged. -#define EMERGENCY_LOG(the_logger, to_log) \ - FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) - -//! Corresponding functions for including the time and date in the log entry. -#define STAMPED_FILTER_LOG(the_logger, to_log, filter) { \ - if (the_logger.member(filter)) { \ - astring temp_log = to_log; \ - if (temp_log.length()) \ - temp_log.insert(0, timely::time_stamp::notarize(true)); \ - the_logger.log(temp_log, filter); \ - } \ -} -//! Time-stamped logging that will always be printed. -#define STAMPED_EMERGENCY_LOG(the_logger, to_log) \ - STAMPED_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) - -//! Class specific logging method that uses a filter. -/* These add a class and function name to the log entry. */ -#define CLASS_FILTER_LOG(the_logger, to_log, filter) { \ - update_current_stack_frame_line_number(__LINE__); \ - if (the_logger.member(filter)) { \ - astring temp_log = to_log; \ - if (temp_log.length()) { \ - temp_log.insert(0, timely::time_stamp::notarize(true)); \ - BASE_FUNCTION(func); \ - temp_log += " ["; \ - temp_log += function_name; \ - temp_log += "]"; \ - } \ - the_logger.log(temp_log, filter); \ - } \ - update_current_stack_frame_line_number(__LINE__); \ -} -//! Class specific logging method that always prints. -#define CLASS_EMERGENCY_LOG(the_logger, to_log) \ - CLASS_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) - -//! Logs information that includes specific class instance information. -/*! This use the instance name of the object, which can include more -information than the simple class name. */ -#define INSTANCE_FILTER_LOG(the_logger, to_log, filter) { \ - update_current_stack_frame_line_number(__LINE__); \ - if (the_logger.member(filter)) { \ - astring temp_log = to_log; \ - if (temp_log.length()) { \ - temp_log.insert(0, timely::time_stamp::notarize(true)); \ - BASE_INSTANCE_FUNCTION(func); \ - temp_log += " ["; \ - temp_log += function_name; \ - temp_log += "]"; \ - } \ - the_logger.log(temp_log, filter); \ - update_current_stack_frame_line_number(__LINE__); \ - } \ -} -//! Logs with class instance info, but this always prints. -#define INSTANCE_EMERGENCY_LOG(the_logger, to_log) \ - INSTANCE_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) - -#endif - diff --git a/core/library/loggers/makefile b/core/library/loggers/makefile deleted file mode 100644 index dceb386c..00000000 --- a/core/library/loggers/makefile +++ /dev/null @@ -1,10 +0,0 @@ -include cpp/variables.def - -PROJECT = loggers -TYPE = library -SOURCE = combo_logger.cpp console_logger.cpp critical_events.cpp file_logger.cpp \ - program_wide_logger.cpp -TARGETS = loggers.lib - -include cpp/rules.def - diff --git a/core/library/loggers/program_wide_logger.cpp b/core/library/loggers/program_wide_logger.cpp deleted file mode 100644 index c91d9771..00000000 --- a/core/library/loggers/program_wide_logger.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*****************************************************************************\ -* * -* Name : program_wide_logger * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "program_wide_logger.h" - -using namespace basis; -using namespace loggers; - -namespace loggers { - -standard_log_base *program_wide_logger::c_the_wide_log = new null_logger; - -standard_log_base &program_wide_logger::get() { return *c_the_wide_log; } - -standard_log_base *program_wide_logger::set(standard_log_base *new_log) -{ - if (!new_log) return NIL; // can't fool me that easily. - standard_log_base *old_log = c_the_wide_log; - c_the_wide_log = new_log; - return old_log; -} - -} //namespace. - diff --git a/core/library/loggers/program_wide_logger.h b/core/library/loggers/program_wide_logger.h deleted file mode 100644 index 70280742..00000000 --- a/core/library/loggers/program_wide_logger.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef PROGRAM_WIDE_LOGGER_FACILITY -#define PROGRAM_WIDE_LOGGER_FACILITY - -/*****************************************************************************\ -* * -* Name : program_wide_logger facility * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "logging_macros.h" - // it seems reasonable to provide the logging macros to any file that is also using - // the program-wide logger. - -#include -#include -#include - -namespace loggers { - -//! A class that provides logging facilities vertically to all of hoople. -/*! - Provides a per-program logging subsystem that can be used from almost - anywhere in the source code. - The program wide logger feature is a globally defined object that - can be switched out to perform different types of logging. - Note: this facility is not thread-safe. The coder must guarantee - that the appropriate logger is set up before cranking up a bunch of - threads that use logging. -*/ - -class program_wide_logger -{ -public: - static loggers::standard_log_base &get(); - //!< Provided by the startup code within each application for logging. - /*!< This can be used like any base_logger object, but the diagnostic items - will be stored into one log file (or other logging mechanism) per - program. */ - - static loggers::standard_log_base *set(loggers::standard_log_base *new_log); - //!< replaces the current program-wide logger with the "new_log". - /*! The "new_log" must be a valid base_logger object. The responsibility - for maintaining "new_log" is taken over by the program-wide logger. - In return, the old program-wide logger is handed back to you and it is - now your responsibility. Be very careful with it if it's owned by other - code and not totally managed by you. */ - -private: - static loggers::standard_log_base *c_the_wide_log; //!< the actual logger. -}; - -////////////// - -//! a trash can for logging; throws away all entries. -/*! - Provides a logger that throws away all of the strings that are logged to it. - This is useful when an interface requires a base_logger but one does not - wish to generate an output file. This object is similar to /dev/null in Unix - (or Linux) and to nul: in Win32. -*/ - -class null_logger : public virtual standard_log_base -{ -public: - virtual ~null_logger() {} - DEFINE_CLASS_NAME("null_logger"); - virtual basis::outcome log(const basis::base_string &info, int filter) { - if (filter || !(&info)) {} - return basis::common::OKAY; - } -}; - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/loggers/standard_log_base.h b/core/library/loggers/standard_log_base.h deleted file mode 100644 index b27fdcd8..00000000 --- a/core/library/loggers/standard_log_base.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef STANDARD_LOG_BASE_CLASS -#define STANDARD_LOG_BASE_CLASS - -////////////// -// Name : standard_log_base -// Author : Chris Koeritz -////////////// -// Copyright (c) 2010-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include "eol_aware.h" -#include "filter_set.h" - -#include - -namespace loggers { - -//! A base class for a very usable logger with a filter_set and eol awareness. -/*! - We add this derived class of base_logger to incorporate some useful functionality - for managing filters without polluting the base class. This class allows the logging - functionality to not deal with a lot of add-ins or chicanery. -*/ - -class standard_log_base -: public virtual basis::base_logger, - public virtual basis::nameable, - public virtual filter_set, - public virtual eol_aware -{ -}; - -} //namespace. - -#endif - diff --git a/core/library/makefile b/core/library/makefile deleted file mode 100644 index c96ec24a..00000000 --- a/core/library/makefile +++ /dev/null @@ -1,31 +0,0 @@ -include variables.def - -PROJECT = core_libraries -BUILD_BEFORE = algorithms \ - basis \ - structures \ - timely \ - mathematics \ - textual \ - nodes \ - filesystem \ - configuration \ - loggers \ - processes \ - application \ - unit_test \ - tests_basis \ - tests_algorithms \ - tests_structures \ - tests_filesystem \ - tests_mathematics \ - tests_nodes \ - tests_textual \ - tests_timely \ - tests_configuration \ - versions \ - crypto \ - tests_crypto - -include rules.def - diff --git a/core/library/mathematics/averager.h b/core/library/mathematics/averager.h deleted file mode 100644 index e45dad9d..00000000 --- a/core/library/mathematics/averager.h +++ /dev/null @@ -1,170 +0,0 @@ -#ifndef AVERAGER_CLASS -#define AVERAGER_CLASS - -/*****************************************************************************\ -* * -* Name : averager * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1997-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace mathematics { - -//! Maintains a list of numbers and provides the average for them. -/*! - The list entries can be weighted if desired. If the list grows too large, - the first chunk of the entries is added as a weighted average and the list's - size is reduced appropriately. -*/ - -template -class averager -{ -public: - averager(int entries = 100, bool compacting = true); - //!< creates an averager whose list length is restricted to "entries". - /*!< if "entries" that is zero, then the maximum length is used. if - "compacting" is true, then the entries which need to be removed during - a compaction will be added to the list as a weighted average. if it's - false, then the unfortunate entries are just eliminated. the first style - leads to a more steady state version of the average while the other is - more recent history. note that the lowest reasonable value for "entries" - is eight due to the compaction algorithm; lower values will work, but the - averager will allow the list to grow to eight anyway. */ - - void add(contents value, int count = 1); - //!< adds a new "value" to the averager, with an optional "count". - - contents average() const { return average(0, length() - 1); } - //!< reports the overall average of the whole list. - - int samples() const; - //!< returns the total number of samples recorded in the average. - - int length() const { return _averages.length(); } - //!< returns the current length of the averages list. - - contents average(int start, int end) const; - //!< reports the average over the range from "start" to "end" inclusive. - - struct weighted_entry { contents value; int count; }; - //!< structure holding a weighted chunk of the average. - /*!< an entry in the list of averages has a "value" and a "count" that - measures the weight with which that "value" is considered. */ - - weighted_entry get(int index) const { return _averages.get(index); } - //!< accesses the entry stored at the "index" specified. - - void compact(); - //!< chops off the oldest portion of the averager. - /*!< if this is a compacting style averager, then the older data is - coalesced and added as a weighted entry. */ - - void check_for_compaction(); - //!< checks whether the averager needs to be compacted yet or not. - /*!< the decision is made according to the maximum allowable size in - "entries" passed to the constructor. if "entries" is zero, the maximum - allowable size is used instead. */ - -private: - bool _do_compaction; //!< do truncated values coalesce to a weighted entry? - basis::array _averages; //!< current list. - int _entries; //!< maximum length of list. -}; - -////////////// - -//! keeps an average on a stream of integers. -class int_averager : public averager -{ -public: - int_averager(int entries = 100, bool compacting = true) - : averager(entries, compacting) {} -}; - -////////////// - -// implementations below... - -const int AVERAGER_SIZE_LIMIT = 180000; // the most items we'll try to handle. - -template -averager::averager(int entries, bool compacting) -: _do_compaction(compacting), _averages(), _entries(entries) -{ - int unit_size = sizeof(weighted_entry); - if (basis::negative(_entries) || !_entries) - _entries = int(AVERAGER_SIZE_LIMIT / unit_size); -} - -template -void averager::compact() -{ - if (length() < 8) return; // sorry, you're too short for this wild ride. - int end_whacking = _averages.length() / 4; - if (_do_compaction) { - contents whacked_average = average(0, end_whacking); - _averages.zap(1, end_whacking); - _averages[0].value = whacked_average; - _averages[0].count = end_whacking + 1; - } else _averages.zap(0, end_whacking); -} - -template -void averager::check_for_compaction() -{ - // make sure that we don't go over our allotted space. - int unit_size = sizeof(weighted_entry); - int limit = basis::minimum(AVERAGER_SIZE_LIMIT, _entries * unit_size); - if (int(_averages.length() + 2) * unit_size >= limit) compact(); -} - -template -void averager::add(contents value, int count) -{ - weighted_entry to_add; - to_add.value = value; - to_add.count = count; - check_for_compaction(); - _averages += to_add; -} - -template -contents averager::average(int start, int end) const -{ - bounds_return(start, 0, length() - 1, 0); - bounds_return(end, start, length() - 1, 0); - bounds_return(end - start + 1, 1, length(), 0); - - contents accum = 0; - contents count = 0; - for (int i = start; i <= end; i++) { - accum += get(i).value * get(i).count; - count += get(i).count; - } - if (!count) count = 1; - return accum / count; -} - -template -int averager::samples() const -{ - int to_return = 0; - for (int i = 0; i < length(); i++) to_return += get(i).count; - return to_return; -} - -} //namespace. - -#endif - diff --git a/core/library/mathematics/chaos.h b/core/library/mathematics/chaos.h deleted file mode 100644 index ffb1a310..00000000 --- a/core/library/mathematics/chaos.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef CHAOS_CLASS -#define CHAOS_CLASS - -////////////// -// Name : chaos -// Author : Chris Koeritz -////////////// -// Copyright (c) 1991-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -#include -#include -#include -#include - -//#define DEBUG_CHAOS - // uncomment for noisy logging. - -#ifdef DEBUG_CHAOS - #include -#endif -#include -#include -#ifdef __UNIX__ - #include -#endif - -namespace mathematics { - -// gets a 32 bit integer from the 15 bit random generator. it's 15 bit because -// rand() only generates numbers up to the MAX_RAND, which in the visual c++ -// case is limited to 0x7FFF. so, we let the first random number generated -// serve as the upper two bytes and we shift another one over a bit to cover -// the whole range of the third and fourth byte, except for that last bit, -// which is added in as a binary random value. -#define GET_32_BIT_RAND_YO \ - basis::un_int ranval = (basis::un_int(rand()) << 16) + (basis::un_int(rand()) << 1) \ - + (basis::un_int(rand()) % 2) - -//! a platform-independent way to acquire random numbers in a specific range. -/*! - This object also re-seeds the underlying system's random seed when retrain() - is invoked. -*/ - -class chaos : public virtual basis::nameable -{ -public: - chaos() { retrain(); } - virtual ~chaos() {} - - DEFINE_CLASS_NAME("chaos"); - - void retrain() - //!< Scrambles the OS's random seed and then provides pseudo-random values. - { - static unsigned int __flaxen = 0; - if (!__flaxen) { - time_t time_num; - time(&time_num); - // "t" is an ugly pointer into system data that contains the time nicely - // broken up into segments. the pointer cannot be freed! - tm *t = localtime(&time_num); - int add_in_milliseconds = basis::environment::system_uptime(); - // create a good random number seed from the time. -#ifdef DEBUG_CHAOS - printf("day %d hour %d min %d sec %d", (int)t->tm_mday, (int)t->tm_hour, - (int)t->tm_min, (int)t->tm_sec); -#endif - // we really don't care very much about the small chance of this being - // initialized to zero again. the chance of that happening more than once - // should be pretty astronomical. - __flaxen = (t->tm_sec + 60 * t->tm_mday + 60 * 31 * t->tm_hour - + 24 * 60 * 31 * t->tm_min) ^ add_in_milliseconds; - // initialize random generator. - srand(int(__flaxen)); - } - } - - // Keep in mind for the inclusive and exclusive functions that a range which - // is too large might not be dealt with well. This is because the random - // functions provided by the O.S. may not support the full range of integers. - - int inclusive(int low, int high) const - //!< Returns a pseudo-random number r, such that "low" <= r <= "high". - { - if (high < low) return low; - unsigned int range = high - low + 1; - GET_32_BIT_RAND_YO; - int adjusted = ranval % range + low; - return adjusted; - } - - int exclusive(int low, int high) const - //!< Returns a pseudo-random number r, such that "low" < r < "high". - { - if (high < low) return low + 1; - unsigned int range = high - low - 1; - GET_32_BIT_RAND_YO; - int adjusted = ranval % range + low + 1; - return adjusted; - } - -}; - -} //namespace. - -#endif - diff --git a/core/library/mathematics/double_plus.h b/core/library/mathematics/double_plus.h deleted file mode 100644 index 25e68345..00000000 --- a/core/library/mathematics/double_plus.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef DOUBLE_PLUS_CLASS -#define DOUBLE_PLUS_CLASS - -/*****************************************************************************\ -* * -* Name : double_plus (an extension for double floating point numbers) * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "math_ops.h" - -#include -#include -#include - -#include //temp - -//! An extension to floating point primitives providing approximate equality. -/*! - Allows a programmer to ignore issues of rounding errors on floating point - numbers by specifying that two floating point numbers are equivalent if - they are equal within a small number "delta". This can help to eliminate - errors in floating point logic. -*/ - -namespace mathematics { - -class double_plus : public basis::orderable -{ -public: - #define DEFAULT_DELTA 0.0001 - /*!< the delta is the acceptable amount of difference between two floating - point numbers that are considered equivalent by this class. if they - differ by more than that, they are considered non-equivalent (and - hence must be greater than or less than each other). */ - - //! initializes using "init" as the initial value and equality within "delta". - double_plus(double init = 0.0, double delta = DEFAULT_DELTA) : c_value(init), c_delta(delta) {} - - //! initializes this from "to_copy". - double_plus(const double_plus &to_copy) : c_value(to_copy.c_value), c_delta(to_copy.c_delta) {} - - virtual ~double_plus() {} - - DEFINE_CLASS_NAME("double_plus"); - - //! standard assignment operator. - double_plus &operator = (const double_plus &cp) - { c_value = cp.c_value; c_delta = cp.c_delta; return *this; } - - double value() const { return truncate(); } - //!< observes the value held in this. - operator double () const { return truncate(); } - //!< observes the value held in this. - - double delta() const { return c_delta; } - //!< observes the precision for equality comparisons. - void delta(double new_delta) { c_delta = new_delta; } - //!< modifies the precision for equality comparisons. - - double truncate() const { return math_ops::round_it(c_value / c_delta) * c_delta; } - //!< returns a version of the number that is chopped off past the delta after rounding. - - //! returns true if this equals "f2" within the "delta" precision. - virtual bool equal_to(const basis::equalizable &f2) const { - const double_plus *cast = dynamic_cast(&f2); - if (!cast) return false; - return this->d_eq(*cast); - } - - //!< returns true if this is less than "f2". - virtual bool less_than(const basis::orderable &f2) const - { - const double_plus *cast = dynamic_cast(&f2); - if (!cast) return false; - return !this->d_eq(*cast) && (c_value < cast->c_value); - } - -private: - double c_value; //!< the contained floating point value. - double c_delta; //!< the offset within which equality is still granted. - - //! returns true if "to_compare" is within the delta of this. - bool d_eq(const double_plus &to_compare) const { - double diff = basis::absolute_value(c_value - to_compare.value()); - return diff < basis::absolute_value(c_delta); - } -}; - -} //namespace. - -#endif - diff --git a/core/library/mathematics/makefile b/core/library/mathematics/makefile deleted file mode 100644 index 08038396..00000000 --- a/core/library/mathematics/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = mathematics -TYPE = library -SOURCE = placeholder.cpp -TARGETS = mathematics.lib - -include cpp/rules.def - diff --git a/core/library/mathematics/math_ops.h b/core/library/mathematics/math_ops.h deleted file mode 100644 index b2f7f879..00000000 --- a/core/library/mathematics/math_ops.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef MATH_OPS_GROUP -#define MATH_OPS_GROUP - -/*****************************************************************************\ -* * -* Name : mathematical operations * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace mathematics { - -//! A grab-bag of mathematical functions that are frequently useful. - -class math_ops -{ -public: - //! returns the rounded integer value for "to_round". - static int round_it(float to_round) - { - int to_return = int(to_round); - // this uses a simplistic view of rounding. - if (to_round - float(to_return) > 0.5) to_return++; - return to_return; - } - - //! returns the rounded integer value for "to_round". - static int round_it(double to_round) - { - int to_return = int(to_round); - // this uses a simplistic view of rounding. - if (to_round - double(to_return) > 0.5) to_return++; - return to_return; - } - -/* - //! returns the number two to the power "raise_to" (i.e. 2^raise_to). - static basis::u_int pow_2(const basis::u_int raise_to) - { - if (!raise_to) return 1; - basis::u_int to_return = 2; - for (basis::u_int i = 1; i < raise_to; i++) - to_return *= 2; - return to_return; - } -*/ - - //! returns n! (factorial), which is n * (n - 1) * (n - 2) ... - static basis::un_int factorial(int n) - { return (n < 2)? 1 : n * factorial(n - 1); } -}; - -} //namespace. - -#endif - diff --git a/core/library/mathematics/placeholder.cpp b/core/library/mathematics/placeholder.cpp deleted file mode 100644 index bc3d86fe..00000000 --- a/core/library/mathematics/placeholder.cpp +++ /dev/null @@ -1,15 +0,0 @@ - - -// sole purpose of this is to make the lib be generated, -// even though we currently do not have any code that lives inside it. - - -#include "double_plus.h" - -using namespace mathematics; - -int fu() -{ -double_plus ted; -return (int)ted; -} diff --git a/core/library/nodes/list.cpp b/core/library/nodes/list.cpp deleted file mode 100644 index 3429f8e7..00000000 --- a/core/library/nodes/list.cpp +++ /dev/null @@ -1,270 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// POLICIES: -// -// the cursor should never be stored to or deleted; it is merely a scanner that -// runs through the list. -// -// the cursor can point at the head or tail. any storage action is taken to -// mean that it applies to the closest real object, if one exists. any query -// action is taken similarly. - -#include "list.h" -#include "node.h" - -#include - -namespace nodes { - -// nice names for the positions of the next link and the previous link in -// our node's indices. -const int PREVIOUS = 0; -const int NEXT = 1; - -////////////// - -// iterator functions: - -void list::iterator::operator ++() -{ - if (is_tail()) return; // already at the end. - _cursor = _cursor->get_link(NEXT); -} - -void list::iterator::operator --() -{ - if (is_head()) return; // already at the front. - _cursor = _cursor->get_link(PREVIOUS); -} - -bool list::iterator::operator ==(const iterator &to_compare) const -{ return _cursor == to_compare._cursor; } - -const node *list::iterator::observe() -{ - if (!_manager || _manager->empty()) return NIL; - if (*this == _manager->head()) next(); - if (*this == _manager->tail()) previous(); - return _cursor; -} - -node *list::iterator::access() -{ - if (!_manager || _manager->empty()) return NIL; - if (*this == _manager->head()) next(); - if (*this == _manager->tail()) previous(); - return _cursor; -} - -bool list::iterator::is_head() const -{ - if (!_manager) return false; - return _cursor == _manager->_head; -} - -bool list::iterator::is_tail() const -{ - if (!_manager) return false; - return _cursor == _manager->_tail; -} - -void list::iterator::jump_head() -{ - if (!_manager) return; - _cursor = _manager->_head; -} - -void list::iterator::jump_tail() -{ - if (!_manager) return; - _cursor = _manager->_tail; -} - -////////////// - -list::list() -: _head(NIL), _tail(NIL) -{ - _head = new node(2); - _tail = new node(2); - _head->set_link(NEXT, _tail); - _tail->set_link(PREVIOUS, _head); -} - -list::~list() -{ - iterator zapper = head(); - while (!empty()) - zap(zapper); - WHACK(_head); - WHACK(_tail); -} - -bool list::empty() const -{ - if (_head->get_link(NEXT) == _tail) return true; - return false; -} - -bool list::set_index(iterator &where, int new_index) -{ - if (where._manager != this) return false; - if (empty()) return false; - node *skipper = _head->get_link(NEXT); - for (int i = 0; i < new_index; i++) { - skipper = skipper->get_link(NEXT); - if (skipper == _tail) return false; // out of bounds now. - } - where._cursor = skipper; - return true; -} - -bool list::forward(iterator &where, int count) -{ - if (where._manager != this) return false; - if (count <= 0) return true; - if (items_from_tail(where) < count) return false; - if (where.is_head()) where.next(); // skip the head guard. - for (int i = 0; i < count; i++) where.next(); - return true; -} - -bool list::backward(iterator &where, int count) -{ - if (where._manager != this) return false; - if (count <= 0) return true; - if (items_from_head(where) < count) return false; - if (where.is_tail()) where.previous(); // skip the tail guard. - for (int i = 0; i < count; i++) where.previous(); - return true; -} - -int list::elements() const -{ - if (empty()) return 0; - int to_return = 0; - node *skipper = _head->get_link(NEXT); - while (skipper != _tail) { - to_return++; - skipper = skipper->get_link(NEXT); - } - return to_return; -} - -int list::items_from_head(const iterator &where) const -{ - if (where._manager != this) return 0; - if (where.is_head()) return 0; // make sure it's not there already. - int index = 0; - node *skipper = _head->get_link(NEXT); - while ( (where._cursor != skipper) && (skipper != _tail) ) { - index++; - skipper = skipper->get_link(NEXT); - } - return index; -} - -int list::items_from_tail(const iterator &where) const -{ - if (where._manager != this) return 0; - if (where.is_tail()) return 0; // make sure it's not there already. - int index = 0; - node *skipper = _tail->get_link(PREVIOUS); - while ( (where._cursor != skipper) && (skipper != _head) ) { - index++; - skipper = skipper->get_link(PREVIOUS); - } - return index; -} - -/* -node *list::get() -{ - if (empty()) return NIL; // make sure the list isn't empty. - if (_cursor == _head) return _head->get_link(NEXT); - // check special case for pointing at the head. - if (_cursor == _tail) return _tail->get_link(PREVIOUS); - // check special case for pointing at the tail. - return _cursor; -} -*/ - -node *list::remove(iterator &where) -{ - if (where._manager != this) return NIL; - if (empty()) return NIL; - if (where._cursor == _head) - where._cursor = _head->get_link(NEXT); - if (where._cursor == _tail) - where._cursor = _tail->get_link(PREVIOUS); - node *old_cursor = where._cursor; - node *old_previous = old_cursor->get_link(PREVIOUS); - node *old_next = old_cursor->get_link(NEXT); - old_cursor->set_link(PREVIOUS, NIL); - old_cursor->set_link(NEXT, NIL); - old_previous->set_link(NEXT, old_next); - old_next->set_link(PREVIOUS, old_previous); - where._cursor = old_next; - return old_cursor; -} - -void list::zap(iterator &where) { delete remove(where); } - -void list::append(iterator &where, node *new_node) -{ - if (where._manager != this) return; - while (new_node->links() < 2) new_node->insert_link(0, NIL); - if (empty()) where._cursor = _head; - if (where._cursor == _tail) - where._cursor = _tail->get_link(PREVIOUS); - // shift from the tail sentinel to the tail element. - node *save_next = where._cursor->get_link(NEXT); - where._cursor->set_link(NEXT, new_node); - new_node->set_link(PREVIOUS, where._cursor); - new_node->set_link(NEXT, save_next); - save_next->set_link(PREVIOUS, new_node); - where._cursor = new_node; -} - -void list::insert(iterator &where, node *new_node) -{ - if (where._manager != this) return; - while (new_node->links() < 2) new_node->insert_link(0, NIL); - if (empty()) where._cursor = _tail; - if (where._cursor == _head) - where._cursor = _head->get_link(NEXT); - // shift from head sentinel to the head element. - node *save_prior = where._cursor->get_link(PREVIOUS); - where._cursor->set_link(PREVIOUS, new_node); - new_node->set_link(NEXT, where._cursor); - new_node->set_link(PREVIOUS, save_prior); - save_prior->set_link(NEXT, new_node); - where._cursor = new_node; -} - -void list::zap_all() -{ - iterator zapper = head(); - while (!empty()) zap(zapper); -} - -} // namespace. - - - - diff --git a/core/library/nodes/list.h b/core/library/nodes/list.h deleted file mode 100644 index 455ebaa4..00000000 --- a/core/library/nodes/list.h +++ /dev/null @@ -1,190 +0,0 @@ -#ifndef LIST_CLASS -#define LIST_CLASS - -/*****************************************************************************\ -* * -* Name : list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - - - -namespace nodes { - -class node; // forward. - -//! Implements a guarded, doubly linked list structure. -/*! - The list is viewed one element at a time, through the monocular of an - iterator, which keeps track of the current position in the list. the - iterator's cursor can be shifted around at will. nodes can be added to - the list before or after the cursor, and the node pointed at by the cursor - can be queried or modified. Using multiple iterators is fine as long as - you guarantee that they either are all just reading the list or that you - have a valid access pattern of reads and writes such that no iterator's - cursor is affected. Note that this list is not thread safe. -*/ - -class list -{ -public: - list(); - //!< constructs a blank list. - - ~list(); - //!< invalidates all contents of the list and destroys all child nodes. - - int elements() const; - //!< returns the number of items currently in the list. - /*!< this is a costly operation. */ - - bool empty() const; - //!< returns true if the list is empty. - /*!< this is really quick compared to checking the number of elements. */ - - //! iterators allow the list to be traversed. - /*! NOTE: it is an error to use an iterator on one list with a different - list; the methods will do nothing or return empty results in this - situation. */ - - class iterator { - public: - iterator(const list *mgr, node *start) : _cursor(start), _manager(mgr) {} - //!< opens up an iterator on a list. - /*!< the preferred method to construct an iterator is to use the - head/tail functions in list. */ - - void operator ++(); //!< go to next item. - void operator --(); //!< go to previous item. - void operator ++(int) { ++(*this); } //!< post-fix synonym for ++. - void operator --(int) { --(*this); } //!< post-fix synonym for --. - - - void next() { (*this)++; } //!< synonym for ++. - void previous() { (*this)--; } //!< synonyms for --. - - bool operator ==(const iterator &to_compare) const; - //!< returns true if the two iterators are at the same position. - - bool is_head() const; - //!< returns true if the cursor is at the head of the list. - /*!< Note: head() and tail() only return true if the iterator is - actually positioned _at_ the head or tail guard. for example, if the - cursor is pointing at the last actual element in the list (but not at - the guard itself), then is_tail() returns false. so, in iteration, - your iterator will actually go past the last valid element before - is_tail() returns true. thus it is very important to check is_tail() - *before* looking at the node with observe() or access(), since those - two will shift the list position to the nearest valid node and away - from the guard. */ - bool is_tail() const; - //!< returns true if the cursor is at the tail of the list. - - void jump_head(); //!< set the iterator to the head. - void jump_tail(); //!< set the iterator to the tail. - - const node *observe(); //!< peek at the current element. - /*!< Note: observe() and access() will return the first element if the - list is positioned at the head (or the last if at the tail), and will - not return NIL for these two positions as long as the list has some - elements in it. the cursor will also have been moved to point at the - element that is returned. - Another Note: it is extremely important that you do not mess with the - links owned by the node (or at least the first two links). - A Third Note: these two functions can return NIL if the list is empty. */ - node *access(); //!< obtain access to the current element. - - private: - node *_cursor; //!< the current position. - friend class list; //!< lists have full access to this object. - const list *_manager; //!< our friendly manager. - }; - - iterator head() const { return iterator(this, _head); } - //!< returns an iterator located at the head of the list. - iterator tail() const { return iterator(this, _tail); } - //!< returns an iterator located at the tail of the list. - - int index(const iterator &it) const { return items_from_head(it); } - //!< returns the zero-based index of the cursor's position from the head. - /*!< this is a synonym for items_from_head(). */ - - bool set_index(iterator &to_set, int new_index); - //!< positions the iterator "to_set" at the index specified. - - // storage and retrieval actions. - // Note: all of these methods shift the iterator onto the next valid node - // if it is positioned at the beginning or end of the list. - - void append(iterator &where, node *new_node); - //!< adds a "new_node" into the list immediately after "where". - /*!< the nodes previously following the current node (if any) will follow - after the "new_node". this does not change the current position. - ownership of "new_node" is taken over by the list. */ - - void insert(iterator &where, node *new_node); - //!< places a "new_node" immediately before the current node in the list. - /*!< the "new_node" will follow the prior precursor to the current node. - this does not change the current position. ownership of "new_node" - is taken over by the list. after the call, the iterator points at - the new node. */ - - node *remove(iterator &where); - //!< extracts the current item from "where" and repairs the hole. - /*!< NIL is returned if the list was empty. the current position is set - to the node that used to be after the node that has been removed. after - the call, the iterator points at the new node. */ - - void zap(iterator &where); - //!< wipes out the item at "where", including its contents. - /*!< the current position is reset to the item after the now defunct - item (or the tail). */ - - void zap_all(); - //!< destroys all the list elements and makes the list empty. - /*!< any existing iterators will be invalidated by this. */ - - // the following positioning functions could fail if the request is out of - // bounds. for example, forward cannot go beyond the end of the list. in - // such cases, false is returned and the list pointer is not moved. - - bool forward(iterator &where, int count); - //!< moves the list pointer "count" items towards the tail. - /*!< Note: forward and backward operate on elements; the head and tail - guard are not included in the count. Also, negative values of "count" - are ignored. */ - bool backward(iterator &where, int count); - //!< moves the list pointer "count" items towards the head. - - int items_from_head(const iterator &where) const; - //!< Returns the number of elements between the current item and the head. - /*!< zero is returned if this is at the first element or the head. */ - int items_from_tail(const iterator &where) const; - //!< Returns the number of elements between the current item and the tail. - /*!< zero is returned if this is at the last element or the tail. */ - -private: - friend class iterator; - node *_head; //!< pointer to the first item. - node *_tail; //!< pointer to the last item. - - bool skip_or_ignore(iterator &where, int count); - //!< zips the list to the position indicated by "count", if it can. - - // not permitted. - list(const list &); - list &operator =(const list &); -}; - -} // namespace. - -#endif - diff --git a/core/library/nodes/makefile b/core/library/nodes/makefile deleted file mode 100644 index b114f9e7..00000000 --- a/core/library/nodes/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = nodes -TYPE = library -SOURCE = list.cpp node.cpp packable_tree.cpp path.cpp symbol_tree.cpp tree.cpp -TARGETS = nodes.lib - -include cpp/rules.def - diff --git a/core/library/nodes/node.cpp b/core/library/nodes/node.cpp deleted file mode 100644 index 465fa5df..00000000 --- a/core/library/nodes/node.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/*****************************************************************************\ -* * -* Name : node * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "node.h" - -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace nodes { - -// the internal_link class anonymously hangs onto a pointer to the object. -struct internal_link { - node *_connection; - internal_link(node *destination = NIL) : _connection(destination) {} - virtual ~internal_link() { _connection = NIL; } -}; - -class node_link_amorph : public amorph -{ -public: - node_link_amorph(int num) : amorph(num) {} -}; - -////////////// - -node::node(int number_of_links) -: _links(new node_link_amorph(number_of_links)) -{ for (int i = 0; i < number_of_links; i++) set_empty(i); } - -node::~node() -{ - _links->reset(); - WHACK(_links); -} - -int node::links() const { return _links->elements(); } - -// set_empty: assumes used correctly by internal functions--no bounds return. -void node::set_empty(int link_num) -{ - internal_link *blank_frank = new internal_link(NIL); - _links->put(link_num, blank_frank); -} - -#define test_arg(link_num) bounds_return(link_num, 0, _links->elements()-1, ); - -void node::set_link(int link_number, node *new_link) -{ - test_arg(link_number); - (*_links)[link_number]->_connection = new_link; -} - -void node::zap_link(int link_number) -{ - test_arg(link_number); - _links->zap(link_number, link_number); -} - -void node::insert_link(int where, node *to_insert) -{ - // make sure that the index to insert at will not be rejected by the - // amorph insert operation. - if (where > links()) - where = links(); - _links->insert(where, 1); - set_empty(where); - set_link(where, to_insert); -} - -node *node::get_link(int link_number) const -{ - bounds_return(link_number, 0, _links->elements()-1, NIL); - return (*_links)[link_number]->_connection; -} - -int node::which(node *branch_to_find) const -{ - int to_return = common::NOT_FOUND; - for (int i = 0; i <= links() - 1; i++) - if (branch_to_find == get_link(i)) { - to_return = i; - break; - } - return to_return; -} - -} // namespace. - diff --git a/core/library/nodes/node.h b/core/library/nodes/node.h deleted file mode 100644 index 1b1a6d64..00000000 --- a/core/library/nodes/node.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef NODE_CLASS -#define NODE_CLASS - -/*****************************************************************************\ -* * -* Name : node * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace nodes { - -// forward: -class node_link_amorph; - -//! An object representing the interstitial cell in most linked data structures. -/*! - The node is intended as an extensible base class that provides general - connectivity support. Nodes can be connected to each other in - arbitrary ways, but often a derived data type provides more structured - organization. When a node's link is zapped, only the connection is - cut; no destruction is performed. - - Examples: A linked list can be represented as a node with one link that - connects to the succeeding item in the list. A doubly linked list can - be represented by a node with two links; one to the previous node and - the other to the next node. The most general structure might be an - arbitrary graph that can connect nodes to any number of other nodes. -*/ - -class node : public virtual basis::root_object -{ -public: - node(int number_of_links = 0); - //!< the constructor provides for "number_of_links" links initially. - /*!< the table below gives some common numbers for links for a variety of - data structures: @code - - Links Data Structure Purpose of Link(s) - ------ ------------------------- ---------------------------------- - 1 singly linked list points to next node in list - 2 doubly linked list one to previous node, one to next - 2 binary tree point to the two children - n n-ary tree point to the n children - n+1 n-ary doubly linked tree point to n children and 1 parent - m m-ary graph node point to m relatives. - @endcode */ - - virtual ~node(); - //!< the destructor simply invalidates the node. - /*!< this does not rearrange the links as would be appropriate for a - data structure in which only the node to be destroyed is being eliminated. - policies regarding the correct management of the links must be made in - objects derived from node. */ - - int links() const; - //!< Returns the number of links the node currently holds. - - void set_link(int link_number, node *new_link); - //!< Connects the node "new_link" to this node. - /*!< the previous link is lost, but not modified in any way. the address - of the new link is used directly--no copy of the node is made. setting a - link to a null node pointer clears that link. */ - - node *get_link(int link_number) const; - //!< Returns the node that is connected to the specified "link_number". - /*!< if the link is not set, then NIL is returned. */ - - void zap_link(int link_number); - //!< the specified link is removed from the node. - - void insert_link(int where, node *to_add = NIL); - //!< adds a new link prior to the position specified in "where". - /*!< thus a "where" value of less than or equal to zero will add a new - link as the first element. a "where" value greater than or equal to - links() will add a new link after the last element. the node "to_add" - will be stored in the new link. */ - - int which(node *to_find) const; - //!< locates the index where "to_find" lives in our list of links. - /*!< returns the link number for a particular node that is supposedly - connected to this node or common::NOT_FOUND if the node is not one - of the children. */ - -private: - node_link_amorph *_links; //!< the list of connections to other nodes. - - void set_empty(int link_number); - //!< clears the link number specified. - - // disallowed: - node(const node &); - node &operator =(const node &); -}; - -////////////// - -//! the basket class holds an object and supports connecting them as nodes. -/*! - the templated object is required to provide both a default constructor - and a copy constructor. -*/ - -template -class basket : public node -{ -public: - basket(int links, const contents &to_store = contents()) - : node(links), _storage(to_store) {} - - basket(const basket &to_copy) { *this = to_copy; } - - basket &operator = (const contents &to_copy) - { if (&to_copy != this) _storage = to_copy; return *this; } - - const contents &stored() const { return _storage; } - //!< allows a peek at the stored object. - contents &stored() { return _storage; } - //!< provides access to the stored object. - -private: - contents _storage; -}; - -} // end namespace. - -#endif - diff --git a/core/library/nodes/packable_tree.cpp b/core/library/nodes/packable_tree.cpp deleted file mode 100644 index a38f88ad..00000000 --- a/core/library/nodes/packable_tree.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/*****************************************************************************\ -* * -* Name : packable_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "packable_tree.h" - -#include -#include -#include -#include -#include - -using namespace basis; -using namespace structures; - -//#define DEBUG_PACKABLE_TREE - // uncomment for noisy debugging. - -#undef LOG -#ifdef DEBUG_PACKABLE_TREE - #include - #define LOG(to_print) printf("%s\n", astring(to_print).s()); -#else - #define LOG(s) { if (!!s) {} } -#endif - -namespace nodes { - -// tree commands are used to tell the unpacker what to do with the blobs -// it finds. BRANCHES_FOLLOW indicates that there are a few branches stored -// at the next few contiguous memory positions. ATTACH_BRANCHES means that -// the next branch should be the parent of some number of previous branches. -// FINISH means that the tree is done being stored (or reconstructed). -enum tree_commands { BRANCHES_FOLLOW, ATTACH_BRANCHES, FINISH }; - -////////////// - -packable_tree_factory::~packable_tree_factory() {} - -////////////// - -//! the TCU stores a command about this packed unit's purpose, the number of branches held, and the size of the contents at this node. -struct tree_command_unit : public virtual packable -{ - tree_commands command; - int number; - int size; - - virtual ~tree_command_unit() {} - - virtual int packed_size() const { return 3 * PACKED_SIZE_INT32; } - - virtual void pack(byte_array &packed_form) const { - attach(packed_form, int(command)); - attach(packed_form, number); - attach(packed_form, size); - } - - virtual bool unpack(byte_array &packed_form) { - int cmd; - if (!detach(packed_form, cmd)) return false; - command = (tree_commands)cmd; - if (!detach(packed_form, number)) return false; - if (!detach(packed_form, size)) return false; - return true; - } -}; - -////////////// - -packable_tree::packable_tree() : tree() {} - -void packable_tree::calcit(int &size_accumulator, const packable_tree *current_node) -{ -LOG(a_sprintf("calcing node %x", current_node)); - FUNCDEF("calcit"); - if (!current_node) throw_error(static_class_name(), func, "current node is nil"); - tree_command_unit temp; - size_accumulator += current_node->packed_size() + temp.packed_size(); -LOG(a_sprintf("len A %d", size_accumulator)); -} - -void packable_tree::packit(byte_array &packed_form, const packable_tree *current_node) -{ -LOG(a_sprintf("packing node %x", current_node)); -LOG(a_sprintf("size A %d", packed_form.length())); - FUNCDEF("packit"); - if (!current_node) throw_error(static_class_name(), func, "current node is nil"); - - byte_array temp_store; - -int guess = current_node->packed_size(); - - current_node->pack(temp_store); - -if (temp_store.length() != guess) -throw_error(current_node->class_name(), func, "failure calculating size"); - - tree_command_unit command; - command.size = temp_store.length(); -//hmmm: do we still need a packed size? - if (current_node->branches() == 0) { - command.command = BRANCHES_FOLLOW; - // the branches following are always just one branch. - command.number = 1; - } else { - command.command = ATTACH_BRANCHES; - command.number = current_node->branches(); - } - // stuff the command unit. - command.pack(packed_form); -LOG(a_sprintf("size B %d", packed_form.length())); - packed_form += temp_store; // main chunk is not packed, just added. -LOG(a_sprintf("size C %d", packed_form.length())); -} - -int packable_tree::recursive_packed_size() const -{ - packable_tree *curr = NIL; - int accum = 0; // where we accumulate the length of the packed form. - for (iterator zip2 = start(postfix); (curr = (packable_tree *)zip2.next()); ) - calcit(accum, curr); - tree_command_unit end_command; - accum += end_command.packed_size(); - return accum; -} - -void packable_tree::recursive_pack(byte_array &packed_form) const -{ - packable_tree *curr = NIL; - for (iterator zip2 = start(postfix); (curr = (packable_tree *)zip2.next()); ) - packit(packed_form, curr); - - tree_command_unit end_command; - end_command.number = 1; - end_command.command = FINISH; - end_command.size = 0; - // end command is stored at end. - end_command.pack(packed_form); -} - -packable_tree *packable_tree::recursive_unpack(byte_array &packed_form, - packable_tree_factory &creator) -{ - stack accumulated_trees(0); // unbounded. - tree_command_unit cmd; - // get the first command out of the package. - if (!cmd.unpack(packed_form)) { -//complain. - return false; - } - - packable_tree *new_branch = NIL; - bool failure = false; // set to true if errors occurred. - - // the packed tree is traversed by grabbing a command and then doing what - // it says as far as pulling in children or adding a new branch. - while (cmd.command != FINISH) { - new_branch = creator.create(); - - new_branch->unpack(packed_form); - - if (cmd.command == ATTACH_BRANCHES) { - if (cmd.number > accumulated_trees.size()) { -//log instead: "badly formed packed tree" - failure = true; - break; - } - for (int i = cmd.number; i > 0; i--) { - packable_tree *to_add = (packable_tree *)accumulated_trees - [accumulated_trees.size()-i]; - new_branch->attach(to_add); - } - packable_tree *junk; - for (int j = 0; j < cmd.number; j++) - accumulated_trees.acquire_pop(junk); - accumulated_trees.push(new_branch); - } else if (cmd.command == BRANCHES_FOLLOW) { - accumulated_trees.push(new_branch); - } else { -//log instead: "invalid command in packed tree" - failure = true; - break; - } - if (!cmd.unpack(packed_form)) { -//complain. - failure = true; - break; - } - } - - if (accumulated_trees.size() != 1) { -//log instead: "not all branches were claimed" - failure = true; - } else if (!failure) { - packable_tree *junk; - accumulated_trees.acquire_pop(junk); - } - - // clean up the allocated objects if we saw a failure. - if (failure) { - while (true) { - packable_tree *to_whack; - outcome ret = accumulated_trees.acquire_pop(to_whack); - if (ret == common::IS_EMPTY) break; - if (to_whack != new_branch) - WHACK(to_whack); - } - WHACK(new_branch); - } - - return new_branch; -} - -} // namespace. - diff --git a/core/library/nodes/packable_tree.h b/core/library/nodes/packable_tree.h deleted file mode 100644 index a003db04..00000000 --- a/core/library/nodes/packable_tree.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef PACKABLE_TREE_CLASS -#define PACKABLE_TREE_CLASS - -/*****************************************************************************\ -* * -* Name : packable_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "tree.h" - -#include - -namespace nodes { - -// forward. -class packable_tree_factory; - -//! A tree object that can be packed into an array of bytes and unpacked again. - -class packable_tree : public tree, public virtual basis::packable -{ -public: - packable_tree(); - //!< constructs a new tree with a root and zero branches. - - // the recursive packing methods here will operate on all nodes starting at this one - // and moving downwards to all branches. - - int recursive_packed_size() const; - //!< spiders the tree starting at this node to calculate the packed size. - - void recursive_pack(basis::byte_array &packed_form) const; - //!< packs the whole tree starting at this node into the packed form. - - static packable_tree *recursive_unpack(basis::byte_array &packed_form, - packable_tree_factory &creator); - //!< unpacks a tree stored in "packed_form" and returns it. - /*!< if NIL is returned, then the unpack failed. the "creator" is needed - for making new derived packable_tree objects of the type stored. */ - - // standard pack, unpack and packed_size methods must be implemented by the derived tree. - -private: - static void packit(basis::byte_array &packed_form, const packable_tree *current_node); - //!< used by our recursive packing methods. - static void calcit(int &size_accumulator, const packable_tree *current_node); - //!< used by recursive packed size calculator. -}; - -////////////// - -class packable_tree_factory -{ -public: - virtual ~packable_tree_factory(); - virtual packable_tree *create() = 0; - //!< a tree factory is needed when we are recreating the packed tree. - /*!< this is because the real type held is always a derived object. - this method should just create a blank object of the appropriate type. */ -}; - -} // namespace. - -#endif - diff --git a/core/library/nodes/path.cpp b/core/library/nodes/path.cpp deleted file mode 100644 index d84325a7..00000000 --- a/core/library/nodes/path.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/*****************************************************************************\ -* * -* Name : path * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "node.h" -#include "path.h" - -#include -#include - -#include - -using namespace basis; -using namespace structures; - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -namespace nodes { - -class path_node_stack : public stack -{ -public: - path_node_stack() : stack(0) {} -}; - -////////////// - -path::path(const node *start) -: _stack(new path_node_stack) -{ _stack->push(const_cast(start)); } - -path::path(const path &to_copy) -: _stack(new path_node_stack(*to_copy._stack)) -{} - -path::~path() -{ - while (_stack->elements()) _stack->pop(); - WHACK(_stack); -} - -node *path::operator [] (int index) const { return (*_stack)[index]; } - -int path::size() const { return _stack->size(); } - -node *path::root() const { return (*_stack)[0]; } - -node *path::current() const { return _stack->top(); } - -node *path::follow() const { return _stack->top(); } - -path &path::operator = (const path &to_copy) -{ - if (this == &to_copy) return *this; - *_stack = *to_copy._stack; - return *this; -} - -node *path::pop() -{ - node *to_return; - if (_stack->acquire_pop(to_return) != common::OKAY) - return NIL; - return to_return; -} - -outcome path::push(node *to_add) -{ return _stack->push(to_add); } - -outcome path::push(int index) -{ - if (!_stack->top()->get_link(index)) return common::NOT_FOUND; - return _stack->push(_stack->top()->get_link(index)); -} - -bool path::generate_path(node *to_locate, path &to_follow) const -{ - FUNCDEF("generate_path") - -if (to_locate || to_follow.current()) {} -LOG("hmmm: path::generate_path is not implemented."); -return false; -} - -} // namespace. - diff --git a/core/library/nodes/path.h b/core/library/nodes/path.h deleted file mode 100644 index af6261f4..00000000 --- a/core/library/nodes/path.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef PATH_CLASS -#define PATH_CLASS - -/*****************************************************************************\ -* * -* Name : path * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace nodes { - -// forward: -class node; -class path_node_stack; - -//! A method for tracing a route from a tree's root to a particular node. -/*! - A path has a starting point at a particular node and a list of links to - take from that node to another node. For the path to remain valid, none - of the links contained within it may be destroyed. -*/ - -class path : public virtual basis::nameable -{ -public: - path(const node *root); - //!< the path is relative to the "root" node. - /*!< this will be the first object pushed onto the stack that we use - to model the path. */ - - path(const path &to_copy); - - ~path(); - - DEFINE_CLASS_NAME("path"); - - path &operator =(const path &to_copy); - - int size() const; - //!< returns the number of items in the path. - - node *root() const; - //!< returns the relative root node for this path. - - node *current() const; - node *follow() const; - //!< Returns the node specified by this path. - /*!< if the path is not valid, NIL is returned. */ - - node *pop(); - //!< returns the top node on the path stack. - /*!< this returns the node at the farthest distance from the relative - root node and removes it from this path. */ - - basis::outcome push(node *to_add); - //!< puts the node "to_add" on the top of the stack. - /*!< adds a node to the path as long as "to_add" is one of the current - node's descendants. */ - - basis::outcome push(int index); - //!< indexed push uses the current top node's index'th link as new top. - /*!< adds the node at "index" of the current top node to the path, - providing that such a link number exists on the current node. */ - - bool generate_path(node *to_locate, path &to_follow) const; - //!< finds the way to get from the root to the "to_locate" node. - /*!< returns true if there is a path between the relative root of - this path and the node "to_locate". if there is such a path, - "to_follow" is set to one of the possible paths. */ - - node *operator [] (int index) const; - //!< returns the node stored at "index", or NIL if "index" is invalid. - -private: - path_node_stack *_stack; //!< implementation of our pathway. -}; - -} // namespace. - -#endif - diff --git a/core/library/nodes/symbol_tree.cpp b/core/library/nodes/symbol_tree.cpp deleted file mode 100644 index a4e455a5..00000000 --- a/core/library/nodes/symbol_tree.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/*****************************************************************************\ -* * -* Name : symbol_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "symbol_tree.h" - -#include -#include -#include -#include - -//#define DEBUG_SYMBOL_TREE - // uncomment for totally noisy version. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -using namespace basis; -using namespace structures; -using namespace textual; - -namespace nodes { - -class symbol_tree_associations : public symbol_table -{ -public: - symbol_tree_associations(int estimated_elements) - : symbol_table(estimated_elements) {} -}; - -////////////// - -symbol_tree::symbol_tree(const astring &node_name, int estimated_elements) -: tree(), - _associations(new symbol_tree_associations(estimated_elements)), - _name(new astring(node_name)) -{ -} - -symbol_tree::~symbol_tree() -{ - WHACK(_name); - WHACK(_associations); -} - -int symbol_tree::children() const { return _associations->symbols(); } - -const astring &symbol_tree::name() const { return *_name; } - -int symbol_tree::estimated_elements() const { return _associations->estimated_elements(); } - -void symbol_tree::rehash(int estimated_elements) { _associations->rehash(estimated_elements); } - -void symbol_tree::hash_appropriately(int estimated_elements) -{ _associations->hash_appropriately(estimated_elements); } - -bool symbol_tree::add(symbol_tree *to_add) -{ -#ifdef DEBUG_SYMBOL_TREE - FUNCDEF("add"); - LOG(astring("adding node for ") + to_add->name()); -#endif - attach(to_add); // add to tree. - _associations->add(to_add->name(), to_add); // add to associations. - return true; -} - -outcome symbol_tree::prune(tree *to_zap_in) -{ -#ifdef DEBUG_SYMBOL_TREE - FUNCDEF("prune"); -#endif - symbol_tree *to_zap = dynamic_cast(to_zap_in); - if (!to_zap) - throw("error: symbol_tree::prune: wrong type of node in prune"); -#ifdef DEBUG_SYMBOL_TREE - LOG(astring("zapping node for ") + to_zap->name()); -#endif - int found = which(to_zap); // find the node in the tree. - if (negative(found)) return common::NOT_FOUND; // not found. -#ifdef DEBUG_SYMBOL_TREE - int kids = _associations->symbols(); -#endif - _associations->whack(to_zap->name()); // remove from associations. -#ifdef DEBUG_SYMBOL_TREE - if (kids - 1 != _associations->symbols()) - throw("error: symbol_tree::prune: failed to crop kid in symtab"); -#endif - tree::prune(to_zap); // remove from tree. - return common::OKAY; -} - -symbol_tree *symbol_tree::branch(int index) const -{ return dynamic_cast(tree::branch(index)); } - -// implementation snagged from basis/shell_sort. -void symbol_tree::sort() -{ - int n = branches(); - symbol_tree *temp; - int gap, i, j; - for (gap = n / 2; gap > 0; gap /= 2) { - for (i = gap; i < n; i++) { - for (j = i - gap; j >= 0 && branch(j)->name() > branch(j + gap)->name(); - j = j - gap) { - // swap the elements that are disordered. - temp = branch(j + gap); - prune_index(j + gap); - insert(j, temp); - temp = branch(j + 1); - prune_index(j + 1); - insert(j + gap, temp); - } - } - } -} - -symbol_tree *symbol_tree::find(const astring &to_find, find_methods how, - string_comparator_function *comp) -{ -#ifdef DEBUG_SYMBOL_TREE - FUNCDEF("find"); -#endif - if (comp == NIL) comp = astring_comparator; -#ifdef DEBUG_SYMBOL_TREE - LOG(astring("finding node called ") + to_find); -#endif - // ensure that we compare the current node. - if (comp(name(), to_find)) return this; - - // perform the upward recursion first, since it's pretty simple. - if (how == recurse_upward) { - symbol_tree *our_parent = dynamic_cast(parent()); - if (!our_parent) return NIL; // done recursing. - return our_parent->find(to_find, how, comp); - } - - // see if our branches match the search term. - symbol_tree **found = _associations->find(to_find, comp); -#ifdef DEBUG_SYMBOL_TREE - if (!found) LOG(to_find + " was not found.") - else LOG(to_find + " was found successfully."); -#endif - if (!found) { - if (how == recurse_downward) { - // see if we can't find that name in a sub-node. - symbol_tree *answer = NIL; - for (int i = 0; i < branches(); i++) { - // we will try each branch in turn and see if it has a child named - // appropriately. - symbol_tree *curr = dynamic_cast(branch(i)); -#ifdef DEBUG_SYMBOL_TREE - LOG(astring("recursing to ") + curr->name()); -#endif - if (curr) - answer = curr->find(to_find, how, comp); - if (answer) - return answer; - } - } - return NIL; - } - return *found; -} - -//hmmm: is this useful elsewhere? -astring hier_prefix(int depth, int kids) -{ - astring indent = string_manipulation::indentation( (depth - 1) * 2); - if (!depth) return ""; - else if (!kids) return indent + "|--"; - else return indent + "+--"; -} - -astring symbol_tree::text_form() const -{ -#ifdef DEBUG_SYMBOL_TREE - FUNCDEF("text_form"); -#endif - astring to_return; - - tree::iterator ted = start(prefix); - // create our iterator to do a prefix traversal. - - tree *curr = (tree *)ted.next(); - -//hmmm: this cast assumes that the tree only contains trees. for more -// safety, we might want a dynamic cast here also. - while (curr) { - // we have a good directory to show. - symbol_tree *curr_cast = dynamic_cast(curr); - if (!curr_cast) { - // something very bad with that... -#ifdef DEBUG_SYMBOL_TREE - LOG("logic error: unknown type in symbol tree."); -#endif - ted.next(); - continue; - } - astring name_to_log = curr_cast->name(); - if (!ted.size()) name_to_log = ""; -#ifdef DEBUG_SYMBOL_TREE - LOG(a_sprintf("depth %d kids %d name %s", ted.size(), curr_cast->branches(), - name_to_log.s())); -#endif - to_return += hier_prefix(curr->depth(), curr_cast->branches()); - to_return += name_to_log; - to_return += parser_bits::platform_eol_to_chars(); - - curr = (tree *)ted.next(); - } - - return to_return; -} - -} // namespace. - diff --git a/core/library/nodes/symbol_tree.h b/core/library/nodes/symbol_tree.h deleted file mode 100644 index c8d4f6dc..00000000 --- a/core/library/nodes/symbol_tree.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef SYMBOL_TREE_CLASS -#define SYMBOL_TREE_CLASS - -/*****************************************************************************\ -* * -* Name : symbol_tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "tree.h" - -#include -#include - -namespace nodes { - -// forward. -class symbol_tree_associations; - -//! A symbol table that supports scope nesting and/or trees of symbol tables. -/*! - Note: although the symbol_tree is a tree, proper functioning is only - guaranteed if you stick to its own add / find methods rather than calling - on the base class's methods... but the tree's iterator support should be - used for traversing the symbol_tree and prune should work as expected. -*/ - -class symbol_tree : public tree -{ -public: - symbol_tree(const basis::astring &node_name, int estimated_elements = 100); - //!< creates a symbol_tree node with the "node_name". - /*!< presumably this could be a child of another symbol tree also. the "estimated_elements" - is used to choose a size for the table holding the names. */ - - virtual ~symbol_tree(); - //!< all child nodes will be deleted too. - /*!< if the automatic child node deletion is not good for your purposes, - be sure to unhook the children before deletion of the tree and manage them - separately. */ - - DEFINE_CLASS_NAME("symbol_tree"); - - int children() const; //!< returns the number of children of this node. - - const basis::astring &name() const; //!< returns the name of this node. - - int estimated_elements() const; //!< returns the number of bits in this node's table. - - symbol_tree *branch(int index) const; //!< returns the "index"th branch. - - void rehash(int estimated_elements); - //!< resizes the underlying symbol_table for this node. - - void hash_appropriately(int estimated_elements); - //!< resizes the hashing parameter to limit bucket sizes. - /*!< rehashes the name table so that there will be no more (on average) - than "max_per_bucket" items per hashing bucket. this is the max that will - need to be crossed to find an item, so reducing the number per bucket - speeds access but also requires more memory. */ - - bool add(symbol_tree *to_add); - //!< adds a child to this symbol_tree. - - virtual basis::outcome prune(tree *to_zap); - //!< removes a sub-tree "to_zap". - /*!< the "to_zap" tree had better be a symbol_tree; we are just matching - the lower-level virtual function prototype. note that the tree node - "to_zap" is not destroyed; it is just plucked from the tree. */ - - enum find_methods { just_branches, recurse_downward, recurse_upward }; - - symbol_tree *find(const basis::astring &to_find, - find_methods how, -///= just_branches, - basis::string_comparator_function *comp = NIL); - //!< returns the node specified by "to_find" or NIL. - /*!< this should be fairly quick due to the symbol table's hashing. - the "how" method specifies the type of recursion to be used in searching - if any. if "how" is passed as "just_branches", then only the branches are - checked and no recursion upwards or downwards is performed. if "how" is - "recurse_downward", then all sub-trees under the branches are checked - also. if "how" is given as "recurse_upward", then "this" node and parent - nodes are checked. the "comp" parameter will be used for comparing the - strings if it's passed as non-NIL. */ - - void sort(); - //!< sorts the sub-nodes of this symbol_tree. - - basis::astring text_form() const; - //!< traverses the tree to build a textual list of the nodes. - -private: - symbol_tree_associations *_associations; //!< the link from names to nodes. - basis::astring *_name; //!< the name of this symbol tree node. -}; - -} // namespace. - -#endif - diff --git a/core/library/nodes/tree.cpp b/core/library/nodes/tree.cpp deleted file mode 100644 index a087b0b7..00000000 --- a/core/library/nodes/tree.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/*****************************************************************************\ -* * -* Name : tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "tree.h" - -#include -#include -#include - -//#define DEBUG_TREE - // uncomment if you want lots of debugging info. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -using namespace basis; - -namespace nodes { - -const int BACKWARDS_BRANCH = 0; - // BACKWARDS_BRANCH is the branch from this tree to its parent. this is - // steeped in the perspective that the root is the backwards direction (where - // we came from, in a sense) and that the children of this node are the - // forwards direction. - -////////////// - -// iterator methods: - -tree::iterator::iterator(const tree *initial, traversal_directions direction) -: path(initial), _order(direction), _aim(AWAY_FROM_ROOT) -{ -} - -tree::iterator::~iterator() { while (size()) pop(); } - -bool tree::iterator::next_node(tree *&to_return) -{ -#ifdef DEBUG_TREE - FUNCDEF("next_node"); -#endif - to_return = NIL; -#ifdef DEBUG_TREE - if ( (_order != to_branches) - && (_order != reverse_branches) ) { - if (_aim == AWAY_FROM_ROOT) LOG("going down") - else LOG("going up"); - } -#endif - switch (_order) { - case prefix: { - if (_aim == AWAY_FROM_ROOT) { - // going down means this is the first time we have seen the current top - // node on the stack. - to_return = (tree *)(*this)[size() - 1]; -#ifdef DEBUG_TREE -// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s()); - if (to_return->branches()) LOG("pushing 0") - else LOG("switching direction"); -#endif - if (to_return->branches()) - push(to_return->branch(0)); - else - _aim = TOWARD_ROOT; - } else { - // going up means that we need to get rid of some things before we - // start seeing new nodes again. - if (size() == 1) return false; - // the last node has been seen.... - tree *last = (tree *)pop(); - tree *current_tree = (tree *)current(); - int lastnum = current_tree->which(last); -#ifdef DEBUG_TREE - if (lastnum < current_tree->branches() - 1) - LOG(a_sprintf("going down %d", lastnum+1)) - else LOG("still going up"); -#endif - if (lastnum < current_tree->branches() - 1) { - _aim = AWAY_FROM_ROOT; - push(current_tree->branch(lastnum + 1)); - } // else still going up. - } - break; - } - case infix: { - if (_aim == AWAY_FROM_ROOT) { - // going down means starting on the left branch. - tree *temp = (tree *)current(); -#ifdef DEBUG_TREE - if (temp->branches()) LOG("pushing 0") - else LOG("switching direction"); -#endif - if (temp->branches()) push(temp->branch(0)); - else { - _aim = TOWARD_ROOT; - to_return = (tree *)current(); -#ifdef DEBUG_TREE -// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s())); -#endif - } - } else { - // going up means that the left branch is done and we need to either - // keep going up or go down the right branch. - if (size() == 1) return false; - // the last node has been seen.... - tree *last = (tree *)pop(); - tree *current_tree = (tree *)current(); - int lastnum = current_tree->which(last); -#ifdef DEBUG_TREE - if (lastnum < 1) LOG(a_sprintf("going down %d", lastnum+1)) - else LOG("still going up"); -#endif - if (lastnum < 1) { - _aim = AWAY_FROM_ROOT; - to_return = (tree *)current(); -#ifdef DEBUG_TREE -/// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s())); -#endif - push(current_tree->branch(lastnum + 1)); - } // else still going up. - } - break; - } - case to_branches: { - if (_aim == TOWARD_ROOT) return false; - else { - if (size() == 1) { - tree *temp = (tree *)current(); - if (!temp->branches()) - _aim = TOWARD_ROOT; - else - push(temp->branch(0)); - } else { - tree *last = (tree *)pop(); - tree *curr = (tree *)current(); - int lastnum = curr->which(last); - if (lastnum < curr->branches() - 1) - push(curr->branch(lastnum + 1)); - else _aim = TOWARD_ROOT; - to_return = last; - } - } - break; - } - case reverse_branches: { - if (_aim == TOWARD_ROOT) return false; - else { - if (size() == 1) { - tree *temp = (tree *)current(); - if (!temp->branches()) _aim = TOWARD_ROOT; - else push(temp->branch(temp->branches() - 1)); - } else { - tree *last = (tree *)pop(); - tree *curr = (tree *)current(); - int lastnum = curr->which(last); - if (lastnum > 0) push(curr->branch(lastnum - 1)); - else _aim = TOWARD_ROOT; - to_return = last; - } - } - break; - } - default: // intentional fall-through to postfix. - case postfix: { - if (_aim == AWAY_FROM_ROOT) { - // going down means that the bottom is still being sought. - tree *temp = (tree *)current(); -#ifdef DEBUG_TREE - if (temp->branches()) LOG("pushing 0") - else LOG("switching direction"); -#endif - if (temp->branches()) push(temp->branch(0)); - else _aim = TOWARD_ROOT; - } else { - // going up means that all nodes below current have been hit. - if (!size()) return false; // the last node has been seen... - else if (size() == 1) { - to_return = (tree *)pop(); - // this is the last node. - return true; - } - tree *last = (tree *)pop(); - to_return = last; -#ifdef DEBUG_TREE -/// LOG(a_sprintf("[%s] ", to_return->get_contents()->held())); -#endif - tree *current_tree = (tree *)current(); - int lastnum = current_tree->which(last); -#ifdef DEBUG_TREE - if (lastnum < current_tree->branches() - 1) - LOG(a_sprintf("going down %d", lastnum+1)) - else LOG("still going up"); -#endif - if (lastnum < current_tree->branches() - 1) { - _aim = AWAY_FROM_ROOT; - push(current_tree->branch(lastnum + 1)); - } // else still going up. - } - break; - } - } - return true; - // it is assumed that termination conditions cause a return earlier on. -} - -void tree::iterator::whack(tree *to_whack) -{ -#ifdef DEBUG_TREE - FUNCDEF("whack"); -#endif - if (!to_whack) return; // that's a bad goof. - if (size()) { - if (to_whack == current()) { - // we found that this is the one at the top right now. - pop(); -#ifdef DEBUG_TREE - LOG("found node in current top; removing it there."); -#endif - } else if (to_whack->parent() == current()) { - // the parent is the current top. make sure we don't mess up traversal. -#ifdef DEBUG_TREE - LOG("found node's parent as current top; don't know what to do."); -#endif - } else { -#ifdef DEBUG_TREE - LOG("found no match for either node to remove or parent in path."); -#endif - } - } - tree *parent = to_whack->parent(); - if (!parent) { -#ifdef DEBUG_TREE - LOG("no parent node for the one to whack! would have whacked " - "root of tree!"); -#endif - } else { - parent->prune(to_whack); - WHACK(to_whack); - } -} - -tree *tree::iterator::next() -{ -#ifdef DEBUG_TREE - FUNCDEF("next"); -#endif - tree *to_return = NIL; - bool found_tree = false; - while (!found_tree) { - bool still_running = next_node(to_return); - if (to_return || !still_running) found_tree = true; - } - return to_return; -} - -////////////// - -// tree methods: - -tree::tree() -: node(1) -{ set_link(BACKWARDS_BRANCH, NIL); } - -tree::~tree() -{ - // must at least unhook ourselves from the parent so we don't become a lost - // cousin. - tree *my_parent = parent(); - if (my_parent) my_parent->prune(this); - - // iterate over the child nodes and whack each individually. - while (branches()) delete branch(0); - // this relies on the child getting out of our branch list. -} - -tree *tree::parent() const { return (tree *)get_link(BACKWARDS_BRANCH); } - -int tree::branches() const { return links() - 1; } - -tree *tree::branch(int branch_number) const -{ - branch_number++; - bounds_return(branch_number, 1, branches(), NIL); - return (tree *)get_link(branch_number); -} - -int tree::which(tree *branch_to_find) const -{ return node::which((node *)branch_to_find) - 1; } - -tree *tree::root() const -{ - const tree *traveler = this; - // keep looking at the backwards branch until it is a NIL. the tree with - // a NIL BACKWARDS_BRANCH is the root. return that tree. - while (traveler->get_link(BACKWARDS_BRANCH)) - traveler = (tree *)traveler->get_link(BACKWARDS_BRANCH); - return const_cast(traveler); -} - -void tree::attach(tree *new_branch) -{ - if (!new_branch) return; - insert_link(links(), new_branch); - new_branch->set_link(BACKWARDS_BRANCH, this); -} - -void tree::insert(int branch_place, tree *new_branch) -{ - branch_place++; - insert_link(links(), NIL); - if (branch_place >= links()) - branch_place = links() - 1; - for (int i = links() - 1; i > branch_place; i--) - set_link(i, get_link(i-1)); - set_link(branch_place, new_branch); - new_branch->set_link(BACKWARDS_BRANCH, this); -} - -outcome tree::prune(tree *branch_to_cut) -{ - int branch_number = which(branch_to_cut); - if (branch_number == basis::common::NOT_FOUND) return basis::common::NOT_FOUND; - return prune_index(branch_number); -} - -outcome tree::prune_index(int branch_to_cut) -{ - branch_to_cut++; - bounds_return(branch_to_cut, 1, branches(), basis::common::NOT_FOUND); - tree *that_branch = (tree *)get_link(branch_to_cut); - that_branch->set_link(BACKWARDS_BRANCH, NIL); - zap_link(branch_to_cut); - return basis::common::OKAY; -} - -int tree::depth() const -{ - tree *my_root = root(); - const tree *current_branch = this; - int deep = 0; - while (current_branch != my_root) { - current_branch = current_branch->parent(); - deep++; - } - return deep; -} - -//probably okay; we want to use this function rather than non-existent -//node base function which isn't implemented yet. -bool tree::generate_path(path &to_follow) const -{ -if (to_follow.size()) {} -/* - tree *traveller = this; - path to_accumulate(root()); - while (traveller->parent() != NIL) { -// int branch_number = traveller->parent()->which(traveller); -// if (branch_number == BRANCH_NOT_FOUND) non_continuable_error -// (class_name(), "generate_path", "branch not found during path construction"); -// chunk *to_stuff = new chunk -// (SELF_OWNED, (byte *)&branch_number, sizeof(int)); - to_accumulate.push(traveller); - traveller = traveller->parent(); - } - // the order of things on the stack needs to be reversed now. -// path to_return = new stack(*to_accumulate.invert()); -// return to_return; - to_accumulate.invert(); - return to_accumulate; -*/ -return false;//temp. -} - -//hmmm: need text form! - -tree::iterator tree::start(traversal_directions direction) const -{ return iterator(this, direction); } - -} // namespace. - diff --git a/core/library/nodes/tree.h b/core/library/nodes/tree.h deleted file mode 100644 index 31d0a99f..00000000 --- a/core/library/nodes/tree.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef TREE_CLASS -#define TREE_CLASS - -/*****************************************************************************\ -* * -* Name : tree * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "node.h" -#include "path.h" - -#include - -namespace nodes { - -//! A dynamically linked tree with an arbitrary number of branches. -/*! - A tree is defined as a node with n branch nodes, where n is dynamic. - Each branch is also a tree. Branch numbers range from 0 through n-1 in - the methods below. Trees can be self-cleaning, meaning that the tree - will destroy all of its children when it is destroyed. - - NOTE: the node indices are not numbered completely obviously; it is better - to use the tree functions for manipulating the node rather than - muddling with it directly. the branch to the tree node's parent is - stored as node zero, in actuality, rather than node zero being the - first branch. -*/ - -class tree : public node -{ -public: - tree(); - //!< constructs a new tree with a root and zero branches. - - virtual ~tree(); - //!< destroys the tree by recursively destroying all child tree nodes. - - DEFINE_CLASS_NAME("tree"); - - virtual tree *branch(int branch_number) const; - //!< Returns the specified branch of this tree. - /*!< NIL is returned if the "branch_number" refers to a branch that - does not exist. */ - - virtual int which(tree *branch_to_find) const; - //!< Returns the branch number for a particular branch in this tree. - /*!< common::NOT_FOUND if the branch is not one of the child nodes. */ - - virtual int branches() const; - //!< Returns the number of branches currently connected to this tree. - - virtual tree *parent() const; - //!< Returns the tree node that is the immediate ancestor of this one. - /*!< if this is the root node, then NIL is returned. */ - - virtual tree *root() const; - //!< Locates and returns the absolute root of the tree containing this tree. - /*!< If this tree IS the absolute root, then "this" is returned. */ - - virtual int depth() const; - //!< Returns the distance of "this" from the root. The root's depth is 0. - - virtual void attach(tree *new_branch); - //!< Attaches the specified branch to the current tree. - - virtual void insert(int branch_place, tree *new_branch); - //!< inserts "new_branch" before the branches starting at "branch_place". - /*!< Places a branch at a particular index and pushes the branches at that - index (and after it) over by one branch. */ - - virtual basis::outcome prune(tree *branch_to_cut); - //!< Removes the specified branch from this tree. - /*!< note that the pruning does not affect the branch being removed; it - just detaches that branch from the tree. if one wants to get rid of the - branch, it should be deleted. if this cannot find the tree specified in - the available branches then the branches of this tree are not touched and - common::NOT_FOUND is returned. */ - virtual basis::outcome prune_index(int branch_to_cut); - //!< Removes the branch at the specified index from this tree. - /*!< if this is given an invalid branch number, then the available - branches then the branches of this tree are not touched and - common::NOT_FOUND is returned. */ - - enum traversal_directions { prefix, infix, postfix, to_branches, - reverse_branches }; - //!< these are the different kinds of tree traversal that are supported. - /*!< "prefix" means that tree nodes will be visited as soon as they are - seen; the deepest nodes have to wait the longest to be seen by the - traversal. "postfix" means that tree nodes are not visited until all of - their ancestors have been visited; the nodes nearer the start of traversal - will wait the longest to be visited. the "infix" direction visits - the left branch, then the starting node, then the right branch. - "infix" is only valid for binary or unary trees; it is an error to - apply "infix" to a tree containing a node that has more than 2 branches. - the direction "to_branches" visits each of the branches in the order - defined by the branch() method. "to_branches" does not visit this - tree's node. "reverse_branches" operates in the opposite direction - of traversal from "to_branches". "reverse_branches" also does not - visit this node. "reverse_branches" can be used to prune off subtrees - during iteration without changing the ordering of the branches; this - is valuable because a pruning operation applied in "to_branches" order - would keep reducing the index of the branches. */ - - enum elevator_directions { TOWARD_ROOT, AWAY_FROM_ROOT }; - //!< movement in the tree is either towards or away from the root. - /*!< distinguishes between motion towards the root node of the tree and - motion away from the root (towards one's children). */ - - class iterator : public path - { - public: - iterator(const tree *initial, traversal_directions direction); - ~iterator(); - - tree *next(); - //!< Returns a pointer to the next tree in the direction of traversal. - /*!< If the traversal is finished, NIL is returned. */ - - void whack(tree *to_whack); - //!< destroys the tree "to_whack". - /*!< whacks the node "to_whack" by patching this iterator so that future - iterations will be correct. it is required that the "to_whack" node - was just returned from a call to next(). - NOTE: this has only been tested with postfix so far. */ - - traversal_directions _order; - elevator_directions _aim; - - private: - bool next_node(tree *&to_return); - //!< sets "to_return" to the next tree in the direction of tree traversal. - /*!< if the next node could not be found in one invocation of next_node, - then "to_return" is set to NIL. the function returns a boolean which - is true only if the iteration process can be continued by another call - to next_node. if the function returns false, the iteration is - complete and "to_return" will always be NIL. */ - }; - - iterator start(traversal_directions direction) const; - //!< Returns a fresh iterator positioned at this tree node. - - virtual bool generate_path(path &to_follow) const; - //!< Returns the path to "this" path_tree from its root. - -private: - // unavailable. - tree(const tree &); - tree &operator =(const tree &); -}; - -} // namespace. - -#endif - diff --git a/core/library/processes/configured_applications.cpp b/core/library/processes/configured_applications.cpp deleted file mode 100644 index 1c8e4617..00000000 --- a/core/library/processes/configured_applications.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/*****************************************************************************\ -* * -* Name : configured_applications -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000 By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "configured_applications.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace structures; -using namespace textual; - -namespace processes { - -//#define DEBUG_APP_CONFIG - // uncomment for noisier debugging version. - -#undef LOG -#define LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) - -////////////// - -const char *PRODUCT_HEADING() { return "product"; } - // the string used for our startup entries as a prefix to the product. - -const char *ASSIGN_TOKEN() { return "="; } - // how we distinguish the key from the value for startup entries. - -const char *SEPARATOR_TOKEN() { return ","; } - // the character between separate key/value pairs in the startup string. - -const char *SEPARATOR_TEXT() { return ", "; } - // the string we use for the separator when printing it. - -const char *PARMS_HEADING() { return "parms"; } - // the tag for parameters in the startup entry. - -const char *ONESHOT_HEADING() { return "oneshot"; } - // the key name for startup entries' flag for once only execution. - -////////////// - -configured_applications::configured_applications(const astring &config_file, - const astring &basename) -: _lock(new mutex), - _config(new ini_configurator(config_file, ini_configurator::RETURN_ONLY, - ini_configurator::APPLICATION_DIRECTORY)), - _sector(new section_manager(*_config, astring(basename) + "_TOC", - astring(PRODUCT_HEADING()) + "_")) -{ -// FUNCDEF("constructor"); - string_table startup_info; - if (!find_section(STARTUP_SECTION(), startup_info)) { - // if there's no startup section, we do nothing right now. - LOG(astring("the startup section doesn't exist yet; adding it now.")); - astring entry = make_startup_entry(basename, "", false); - startup_info.add(STARTUP_APP_NAME(), entry); - add_section(STARTUP_SECTION(), startup_info); - } -} - -configured_applications::~configured_applications() -{ - WHACK(_sector); - WHACK(_config); - WHACK(_lock); -} - -const char *configured_applications::STARTUP_SECTION() -{ return "PRIVATE_STARTUP_LNCH1.0"; } - -const char *configured_applications::STARTUP_APP_NAME() -{ return "placeholder"; } - -bool configured_applications::product_exists(const astring &product) -{ - auto_synchronizer l(*_lock); - if (!_sector->section_exists(product)) return false; - return true; -} - -astring configured_applications::find_program(const astring &product, - const astring &app_name, int &level) -{ - auto_synchronizer l(*_lock); - astring heading = _sector->make_section_heading(product); - astring found = _sector->config().load(heading, app_name, ""); -////////////// -//overly specific bits here... -//hmmm: add this in as a specialization provided by real owner of class. - if (!found) { - // we didn't find the entry under the section we wanted to find it in. - // there are a couple cases where we can kludge this section to a - // different name, based on legacy requirements, and still find the - // right item possibly. - if (product.iequals("supervisor")) { - // for some older installs, they say "supervisor" but mean "core". - heading = _sector->make_section_heading("core"); - found = _sector->config().load(heading, app_name, ""); - } else if (product.iequals("lightlink")) { - heading = _sector->make_section_heading("core"); - found = _sector->config().load(heading, app_name, ""); - if (!found) { - // we can take one more remedial step for this phrase. - heading = _sector->make_section_heading("server"); - found = _sector->config().load(heading, app_name, ""); - } - } - } -//end of overly specific. -////////////// - found = parser_bits::substitute_env_vars(found); - - int comma_loc = found.find(","); - if (negative(comma_loc)) return ""; // couldn't find our priority. - level = found.convert(0); - found.zap(0, comma_loc); - - return found; -} - -bool configured_applications::add_program(const astring &product, - const astring &app_name, const astring &full_path, int level) -{ -#ifdef DEBUG_APP_CONFIG - FUNCDEF("add_program"); -#endif - auto_synchronizer l(*_lock); - bool existed = true; - // lookup the section, if it exists. - string_table info_table; - if (!_sector->section_exists(product)) { - existed = false; - } else - find_section(product, info_table); -#ifdef DEBUG_APP_CONFIG - if (existed) { - LOG(astring("section for ") + product + " found:"); - for (int i = 0; i < info_table.symbols(); i++) - LOG(astring("key=") + info_table.name(i) + " value=" + info_table[i]); - } else LOG(astring("section for ") + product + " not found."); -#endif - // remove any existing entry. - info_table.whack(app_name); - // plug in our new entry. - a_sprintf full_entry("%d,%s", level, full_path.s()); - info_table.add(app_name, full_entry); -#ifdef DEBUG_APP_CONFIG - LOG(astring("new section for ") + product + " has:"); - for (int i = 0; i < info_table.symbols(); i++) - LOG(astring("key=") + info_table.name(i) + " value=" + info_table[i]); -#endif - // now call the proper storage function based on whether the section - // existed before or not. - if (existed) return replace_section(product, info_table); - else return add_section(product, info_table); -} - -bool configured_applications::remove_program(const astring &product, - const astring &app_name) -{ -// FUNCDEF("remove_program"); - auto_synchronizer l(*_lock); - // if the section's missing, there's nothing to remove... - string_table info_table; - if (!find_section(product, info_table)) return true; - // the section did exist, so remove any existing entry. - info_table.whack(app_name); - // now call the proper storage function based on whether the section - // existed before or not. - return replace_section(product, info_table); -} - -bool configured_applications::find_section(const astring §ion_name, - string_table &info_found) -{ -// FUNCDEF("find_section"); - info_found.reset(); - auto_synchronizer l(*_lock); - if (!_sector->find_section(section_name, info_found)) { - LOG(section_name + " was not found in the configuration."); - return false; - } - return true; -} - -bool configured_applications::add_section(const astring §ion_name, - const string_table &info_found) -{ - auto_synchronizer l(*_lock); - return _sector->add_section(section_name, info_found); -} - -bool configured_applications::replace_section(const astring §ion_name, - const string_table &info_found) -{ - auto_synchronizer l(*_lock); - return _sector->replace_section(section_name, info_found); -} - -astring configured_applications::make_startup_entry(const astring &product, - const astring &parms, bool one_shot) -{ - return astring(PRODUCT_HEADING()) + ASSIGN_TOKEN() + product - + SEPARATOR_TEXT() + PARMS_HEADING() + ASSIGN_TOKEN() - + parms + SEPARATOR_TEXT() + ONESHOT_HEADING() + ASSIGN_TOKEN() - + astring(astring::SPRINTF, "%d", one_shot); -} - -bool configured_applications::parse_startup_entry(const astring &info, - astring &product, astring &parms, bool &one_shot) -{ -// FUNCDEF("parse_startup_section"); - // parse the items that are in the entry for this program. - variable_tokenizer entry_parser(SEPARATOR_TOKEN(), ASSIGN_TOKEN()); - entry_parser.parse(info); - // grab the pertinent bits for the program to be started. - product = entry_parser.find(PRODUCT_HEADING()); - parms = entry_parser.find(PARMS_HEADING()); -//LOG(astring("parms=") + parms); - astring once = entry_parser.find(ONESHOT_HEADING()); - one_shot = (bool)once.convert(0); - // we require the product part at least. - if (!product) return false; - return true; -} - -bool configured_applications::find_entry(const string_table &table, - const astring &name, astring &location) -{ - // seek the entry in the table specified. - astring *found = table.find(name); - if (!found) return false; - // found the entry using the name. - location = *found; - return true; -} - -bool configured_applications::add_startup_entry(const astring &product, - const astring &app_name, const astring ¶meters, int one_shot) -{ -// FUNCDEF("add_startup_entry"); - auto_synchronizer l(*_lock); - - LOG(astring("product \"") + product + "\", application \"" + app_name - + (one_shot? astring("\", OneShot") : astring("\", MultiUse"))); - - string_table startup_info; - if (!find_section(STARTUP_SECTION(), startup_info)) { - // if there's no startup section, we can't go on. that should have been - // created during startup of this program. - LOG(astring("internal startup section not found!")); - return false; - } - - astring new_entry = make_startup_entry(product, parameters, - one_shot); - startup_info.add(app_name, new_entry); - if (!replace_section(STARTUP_SECTION(), startup_info)) - return false; -//hmmm: that's a bogus error; this is really an internal fup error. - - return true; -} - -bool configured_applications::remove_startup_entry(const astring &product, - const astring &app_name) -{ -// FUNCDEF("remove_startup_entry"); - auto_synchronizer l(*_lock); - - LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); - - string_table startup_info; - if (!find_section(STARTUP_SECTION(), startup_info)) { - // if there's no startup section, we try to add one. - add_section(STARTUP_SECTION(), startup_info); - // if it still doesn't exist afterwards, we're hosed. - if (!find_section(STARTUP_SECTION(), startup_info)) { -/// COMPLAIN_PRODUCT; -//massive fup of some unanticipated sort. -//complain. - return false; - } - } - - // check that the entry already exists for this program. - astring entry_found; - if (!find_entry(startup_info, app_name, entry_found)) { -// COMPLAIN_APPLICATION; - LOG(astring("no entry was found for ") + app_name); - return false; - } - - startup_info.whack(app_name); - if (!replace_section(STARTUP_SECTION(), startup_info)) { -//what happened with that? - return false; - } - - return true; -} - -} //namespace. - - diff --git a/core/library/processes/configured_applications.h b/core/library/processes/configured_applications.h deleted file mode 100644 index d7c6f776..00000000 --- a/core/library/processes/configured_applications.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef CONFIGURED_APPLICATIONS_CLASS -#define CONFIGURED_APPLICATIONS_CLASS - -/*****************************************************************************\ -* * -* Name : configured_applications -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 2000 By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include - -namespace processes { - -//! Manages the initialization file for a set of registered applications. -/*! - This records the list of programs that are allowed to be executed as well - as the list of applications launched at object startup time. -*/ - -class configured_applications -{ -public: - configured_applications(const basis::astring &config_file, const basis::astring &basename); - //!< manages application settings for in the "config_file". - /*!< the "basename" is used for the section name of a list of products - that can be managed by this class. each product has a set of applications - that are part of the product's full package. */ - - virtual ~configured_applications(); - - // this section has mainly informational functions. - - DEFINE_CLASS_NAME("configured_applications"); - - static const char *STARTUP_SECTION(); - //!< the section where startup info is stored. - - static const char *STARTUP_APP_NAME(); - //!< a special placeholder name that will appear in the startup list. - /*!< it is not to be executed like the other programs for startup. */ - - static bool find_entry(const structures::string_table &table, const basis::astring &name, - basis::astring &location); - //!< returns true if the key "name" for a program is found in the "table". - /*!< the "location" is set to the value found in the table if successful. - */ - - static basis::astring make_startup_entry(const basis::astring &product, - const basis::astring &parms, bool one_shot); - //!< returns the appropriate string for a startup record. - - static bool parse_startup_entry(const basis::astring &info, basis::astring &product, - basis::astring &parms, bool &one_shot); - //!< processes the items in "info" as an application startup list. - /*!< using a string "info" that was listed as the startup entry for an - application, the "product", "parms" and "one_shot" bits are parsed out - and returned. */ - - bool product_exists(const basis::astring &product); - //!< returns true if the section for "product" exists in the TOC. - - basis::astring find_program(const basis::astring &product, const basis::astring &app_name, - int &level); - //!< seeks out the entry for the "product" and "app_name" in our info. - /*!< the returned string will either be empty (on failure) or will contain - the full path to the application (on success). the "level" will specify - the ordering of shutdown, where larger levels are shut down first. */ - - // the following functions actually modify the configuration file. - - bool find_section(const basis::astring §ion_name, structures::string_table &info_found); - //!< locates the entries for "section_name" and stores them in "info_found". - - bool add_section(const basis::astring §ion_name, const structures::string_table &info); - //!< puts a chunk of "info" into the section for "section_name". - /*!< this fails if the section already exists. */ - - bool replace_section(const basis::astring §ion_name, const structures::string_table &info); - //!< replaces the section for "section_name" with "info". - /*!< this fails if the section does not already exist. */ - - bool add_program(const basis::astring &product, const basis::astring &app_name, - const basis::astring &full_path, int level); - //!< registers a program "app_name" into the "product" section. - /*!< the "full_path" specifies where to find the program and the "level" - gives the application an ordering for shutdown. higher levels are shut - down before lower ones. */ - - bool remove_program(const basis::astring &product, const basis::astring &app_name); - //!< takes a previously registered "app_name" out of the list for "product". - - bool add_startup_entry(const basis::astring &product, const basis::astring &app_name, - const basis::astring ¶meters, int one_shot); - //!< establishes the "app_name" as a program launched at object startup. - /*!< adds an entry to the startup section for a program that will be - launched when the application manager restarts. */ - - bool remove_startup_entry(const basis::astring &product, const basis::astring &app_name); - //!< takes an existing entry for the "app_name" out of the startup section. - -private: - basis::mutex *_lock; //!< synchronization protection for our objects. - configuration::ini_configurator *_config; //!< manages our configuration settings. - configuration::section_manager *_sector; //!< keeps track of our product sections. -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/ethread.cpp b/core/library/processes/ethread.cpp deleted file mode 100644 index 30efcc88..00000000 --- a/core/library/processes/ethread.cpp +++ /dev/null @@ -1,328 +0,0 @@ -/*****************************************************************************\ -* * -* Name : ethread * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ethread.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#elif defined(__UNIX__) - #include -#else - #error unknown OS for thread support. -#endif - -using namespace basis; -using namespace loggers; -using namespace structures; -using namespace timely; - -//#define COUNT_THREADS - // if this is enabled, then threads will be counted when they are created - // or destroyed. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -namespace processes { - -const int MAXIMUM_CREATE_ATTEMPTS = 20; - // the number of retries we allow to try creating a thread, if the first - // attempt fails. - -const int MINIMUM_SLEEP_PERIOD = 10; - // this is the smallest time we'll sleep for if we're slack. - -const int MAXIMUM_SLEEP_PERIOD = 200; - // the number of milliseconds we use for breaking up longer sleep periods. - -const int SNOOZE_FOR_RETRY = 100; - // how long to sleep when a thread creation fails. - -#ifdef COUNT_THREADS - // singleton thread counter code. - class thread_counter : public virtual root_object { - public: - thread_counter() : _count(0) {} - DEFINE_CLASS_NAME("thread_counter"); - void increment() { - auto_synchronizer l(_lock); - _count++; - } - void decrement() { - auto_synchronizer l(_lock); - _count--; - } - private: - int _count; - mutex _lock; - }; - - SAFE_STATIC(thread_counter, _current_threads, ) - -//hmmm: this seems to not be used anywhere yet. it needs to be accessible -// externally if it's going to serve any useful purpose. - -#endif - -ethread::ethread() -: _thread_ready(false), - _thread_active(false), - _stop_thread(false), - _data(NIL), -#ifdef __UNIX__ - _handle(new pthread_t), -#elif defined(__WIN32__) - _handle(0), -#endif - _sleep_time(0), - _periodic(false), - _next_activation(new time_stamp), - _how(TIGHT_INTERVAL) // unused. -{ -// FUNCDEF("constructor [one-shot]"); -} - -ethread::ethread(int sleep_timer, timed_thread_types how) -: _thread_ready(false), - _thread_active(false), - _stop_thread(false), - _data(NIL), -#ifdef __UNIX__ - _handle(new pthread_t), -#elif defined(__WIN32__) - _handle(0), -#endif - _sleep_time(sleep_timer), - _periodic(true), - _next_activation(new time_stamp), - _how(how) -{ -// FUNCDEF("constructor [periodic]"); - if (sleep_timer < MINIMUM_SLEEP_PERIOD) { - _sleep_time = MINIMUM_SLEEP_PERIOD; - } -} - -ethread::~ethread() -{ - stop(); - WHACK(_next_activation); -#ifdef __UNIX__ - WHACK(_handle); -#endif -} - -///void ethread::pre_thread() {} - -///void ethread::post_thread() {} - -// the reschedule operation assumes that assignment to a time stamp -// object (based on a real numbers) happens indivisibly. -void ethread::reschedule(int delay) -{ - *_next_activation = time_stamp(delay); // start after the delay. -} - -bool ethread::start(void *thread_data) -{ - FUNCDEF("start"); - if (!thread_finished()) return false; // already running. - _data = thread_data; // store the thread's data pointer. - _stop_thread = false; // don't stop now. - _thread_ready = true; // we're starting it now. - _next_activation->reset(); // make "now" the next time to activate. - bool success = false; - int error = 0; - int attempts = 0; - while (attempts++ < MAXIMUM_CREATE_ATTEMPTS) { -#ifdef __UNIX__ - pthread_attr_t attribs; // special flags for creation of thread. - int aret = pthread_attr_init(&attribs); - if (aret) LOG("failed to init attribs."); - aret = pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED); - if (aret) LOG("failed to set detach state."); - int ret = -1; - if (_periodic) - ret = pthread_create(_handle, &attribs, periodic_thread_driver, - (void *)this); - else - ret = pthread_create(_handle, &attribs, one_shot_thread_driver, - (void *)this); - if (!ret) success = true; - else error = ret; -#elif defined(__WIN32__) - if (_periodic) - _handle = _beginthread(periodic_thread_driver, 0, (void *)this); - else - _handle = _beginthread(one_shot_thread_driver, 0, (void *)this); - if (_handle != -1) success = true; - else error = critical_events::system_error(); -#endif - if (success) break; // got it created. - LOG("failed to create thread; trying again..."); - time_control::sleep_ms(SNOOZE_FOR_RETRY); - } - if (!success) { - // couldn't start it, so reset our state. - LOG(astring("failed to create thread, error is ") - + critical_events::system_error_text(error)); - exempt_stop(); - return false; - } - return true; -} - -void ethread::stop() -{ - cancel(); // tell thread to leave. - if (!thread_started()) return; // not running. - while (!thread_finished()) { -#ifdef __WIN32__ - int result = 0; - if (!GetExitCodeThread((HANDLE)_handle, (LPDWORD)&result) - || (result != STILL_ACTIVE)) { - exempt_stop(); - break; - } -#endif - time_control::sleep_ms(10); // wait for thread to leave. - } -} - -void ethread::exempt_stop() -{ - _thread_active = false; - _thread_ready = false; -#ifdef __WIN32__ - _handle = 0; -#endif -} - -#ifdef __UNIX__ -void *ethread::one_shot_thread_driver(void *hidden_pointer) -#elif defined(__WIN32__) -void ethread::one_shot_thread_driver(void *hidden_pointer) -#else -#error unknown thread signature. -#endif -{ -// FUNCDEF("one_shot_thread_driver"); - ethread *manager = (ethread *)hidden_pointer; -#ifdef __UNIX__ - if (!manager) return NIL; -#else - if (!manager) return; -#endif -#ifdef COUNT_THREADS - _current_threads().increment(); -#endif -/// manager->pre_thread(); - manager->_thread_active = true; - manager->perform_activity(manager->_data); -/// manager->post_thread(); - manager->exempt_stop(); -#ifdef COUNT_THREADS - _current_threads().decrement(); -#endif -#ifdef __UNIX__ - pthread_exit(NIL); - return NIL; -#else - _endthread(); -#endif -} - -#ifdef __UNIX__ -void *ethread::periodic_thread_driver(void *hidden_pointer) -#elif defined(__WIN32__) -void ethread::periodic_thread_driver(void *hidden_pointer) -#else -#error unknown thread signature. -#endif -{ -// FUNCDEF("periodic_thread_driver"); - ethread *manager = (ethread *)hidden_pointer; -#ifdef __UNIX__ - if (!manager) return NIL; -#elif defined(__WIN32__) - if (!manager) return; -#endif -#ifdef COUNT_THREADS - _current_threads().increment(); -#endif -/// manager->pre_thread(); - - while (!manager->_stop_thread) { - // for TIGHT_INTERVAL, we reset the next active time here. this is safe - // relative to the reschedule() method, since we're about to do - // perform_activity() right now anyway. this brings about a pretty hard - // interval; if perform_activity() takes N milliseconds, then there will - // only be sleep_time - N (min zero) ms before the next invocation. - if (manager->_how == TIGHT_INTERVAL) - *manager->_next_activation = time_stamp(manager->_sleep_time); - - manager->_thread_active = true; - manager->perform_activity(manager->_data); - manager->_thread_active = false; - - // SLACK_INTERVAL means between activations. we reset the next activation - // here to ensure we wait the period specified for sleep time, including - // whatever time was taken for the activity itself. - if (manager->_how == SLACK_INTERVAL) - *manager->_next_activation = time_stamp(manager->_sleep_time); - - // we do the sleep timing in chunks so that there's not such a huge wait - // when the user stops the thread before the sleep interval elapses. - // snooze until time for the next activation. - while (!manager->_stop_thread) { - int time_diff = int(manager->_next_activation->value() - - time_stamp().value()); - if (time_diff < 0) time_diff = 0; // time keeps on slipping. - // make sure we take our time if we're slack intervalled. - if (manager->_how == SLACK_INTERVAL) { - if (time_diff < MINIMUM_SLEEP_PERIOD) - time_diff = MINIMUM_SLEEP_PERIOD; - } - if (time_diff > MAXIMUM_SLEEP_PERIOD) - time_diff = MAXIMUM_SLEEP_PERIOD; - if (!manager->_stop_thread) - time_control::sleep_ms(time_diff); - if (time_stamp() >= *manager->_next_activation) - break; - } - } -/// manager->post_thread(); - manager->exempt_stop(); -#ifdef COUNT_THREADS - _current_threads().decrement(); -#endif -#ifdef __UNIX__ - pthread_exit(NIL); - return NIL; -#elif defined(__WIN32__) - _endthread(); -#endif -} - -} //namespace. - diff --git a/core/library/processes/ethread.h b/core/library/processes/ethread.h deleted file mode 100644 index 9adab465..00000000 --- a/core/library/processes/ethread.h +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef ETHREAD_CLASS -#define ETHREAD_CLASS - -/*****************************************************************************\ -* * -* Name : ethread (easy thread) * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -#include - -#ifndef __APPLE__ -#ifdef __UNIX__ -// typedef long unsigned int pthread_t; -#endif -#endif - -namespace processes { - -//! Provides a platform-independent object for adding threads to a program. -/*! - This greatly simplifies creating and managing threads by hiding all the - operating system details. The user just needs to override one virtual - function in their derived object to perform the main activity of their - thread. The thread can be a one time invocation or it can run periodically. - Control over the thread remains in the hands of the program that started - it. -*/ - -class ethread : public virtual basis::root_object -{ -public: - ethread(); - //!< creates a single-shot thread object. - /*!< the OS-level thread is not started until the start() method is - invoked. this constructor creates a thread that will only execute - once; when start() is called, the thread starts up and performs its - activity. it will then stop. to run it again, start() must be invoked - again. however, if the perform_activity() method just keeps running, - then the single-shot thread can live as long as needed. it is important - for such a thread to periodically check should_exit() to avoid having - the program hang-up when it's supposed to be shutting down. */ - - enum timed_thread_types { TIGHT_INTERVAL, SLACK_INTERVAL }; - - ethread(int sleep_timer, timed_thread_types how = SLACK_INTERVAL); - //!< creates a managed thread object that runs on a periodic interval. - /*!< the thread will activate every "sleep_timer" milliseconds. when - start() is invoked, the thread's action (via the perform_activity() - method) will be performed at regular intervals (using the specified value - for "sleep_timer"). the thread will continue activating until the stop() - method is called. a faster interval is used internally during sleep - periods such that calling stop() will not consume the whole "sleep_timer" - period. if the "how" is TIGHT_INTERVAL, then the thread will activate - every "sleep_timer" milliseconds, as accurately as possible. if the "how" - is SLACK_INTERVAL, then the thread will activate after a delay of - "sleep_timer" milliseconds from its last activation. the latter mode - allows the thread to consume its entire intended operation time knowing - that there will still be slack time between when it is active. the - former mode requires the thread to only run for some amount of time less - than its "sleep_timer"; otherwise it will hog a lot of the CPU. */ - - virtual ~ethread(); - - DEFINE_CLASS_NAME("ethread"); - - bool start(void *thread_data); - //!< causes the thread to start, if it has not already been started. - /*!< if the thread has terminated previously, then this will restart the - thread. true is returned if the thread could be started. false is - returned if the thread could not be started or if it is already running. */ - - void stop(); - //!< tells the thread to shutdown and waits for the shutdown to occur. - /*!< this will cause the OS thread to terminate once the current (if any) - perform_activity() invocation completes. the thread may be restarted - with start(). */ - - void cancel() { _stop_thread = true; } - //!< stops the thread but does not wait until it has terminated. - /*!< this is appropriate for use within the perform_activity() method. */ - -// virtual void pre_thread(); - //!< invoked just after after start(), when the OS thread is created. - /*!< the call comes in _from_ the thread itself, so the derived method - must be thread-safe. */ -// virtual void post_thread(); - //!< this is invoked just before the thread is to be terminated. - /*!< the call also comes in from the thread itself, so the implementation - must be thread-safe. */ - - virtual void perform_activity(void *thread_data) = 0; - //!< carries out the main activity of the thread. - /*!< this is called repeatedly by the main thread management function and - so should return as soon as possible. if it does not return fairly - regularly, then the thread shutdown process will not occur until the - function exits on its own. */ - - void exempt_stop(); - //!< this special form of stop() does not wait for the thread to exit. - /*!< it is required in certain weird OS situations where the thread does - not exit properly and stop() would cause an infinite wait. don't use it - unless you are SURE that this is the case. */ - - void reschedule(int delay = 0); - //!< causes a periodic thread to activate after "delay" milliseconds from now. - /*!< this resets the normal activation period, but after the next - activation occurs, the normal activation interval takes over again. */ - - int sleep_time() const { return _sleep_time; } - //!< returns the current periodic thread interval. - /*!< this is only meaningful for periodic threads. */ - - void sleep_time(int new_sleep) { _sleep_time = new_sleep; } - //!< adjusts the period for the thread to the "new_sleep" interval. - /*!< this is only meaningful for periodic threads. */ - - // these functions report on the thread state. - - bool thread_started() const { return _thread_ready; } - //!< returns true if the thread has been started. - /*!< this does not mean it is necessarily active. */ - - bool thread_finished() const { return !_thread_ready; } - //!< returns true if the thread has exited. - /*!< This can happen either by the thread responding to the stop() or - cancel() methods or when the thread stops of its own accord. if this - returns true, it means that the thread will not start up again unless - the user invokes start(). */ - - bool thread_active() const { return _thread_active; } - //!< returns true if the thread is currently performing its activity. - /*!< this information is not necessarily relevant even at the point it is - returned (because of the nature of multethreading), so don't believe this - information for anything important. */ - - bool should_stop() const { return _stop_thread; } - //!< reports whether the thread should stop right now. - /*!< this returns true due to an invocation of stop() or cancel(). */ - -private: - bool _thread_ready; //!< is the thread ready to run (or running)? - bool _thread_active; //!< is the thread currently performing? - bool _stop_thread; //!< true if the thread should stop now. - void *_data; //!< holds the thread's link back to whatever. -#ifdef __UNIX__ - pthread_t *_handle; //!< thread structure for our thread. -#elif defined(__WIN32__) - uintptr_t _handle; //!< thread handle for the active thread, or zero. -#endif - int _sleep_time; //!< threads perform at roughly this interval. - bool _periodic; //!< true if this thread should run repeatedly. - timely::time_stamp *_next_activation; //!< the next time perform_activity is called. - timed_thread_types _how; //!< how is the period evaluated? - - // the OS level thread functions. -#ifdef __UNIX__ - static void *periodic_thread_driver(void *hidden_pointer); - static void *one_shot_thread_driver(void *hidden_pointer); -#elif defined(__WIN32__) - static void periodic_thread_driver(void *hidden_pointer); - static void one_shot_thread_driver(void *hidden_pointer); -#endif - - // forbidden. - ethread(const ethread &); - ethread &operator =(const ethread &); -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/heartbeat.cpp b/core/library/processes/heartbeat.cpp deleted file mode 100644 index 7b3d9f68..00000000 --- a/core/library/processes/heartbeat.cpp +++ /dev/null @@ -1,102 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : heartbeat * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "heartbeat.h" - -#include -#include -#include - -using namespace basis; -using namespace timely; - -namespace processes { - -heartbeat::heartbeat(int misses_allowed, int check_interval) -: _next_heartbeat(new time_stamp()), - _check_interval(0), - _misses_allowed(0), - _misses(0) -{ reset(misses_allowed, check_interval); } - -heartbeat::heartbeat(const heartbeat &to_copy) -: root_object(), - _next_heartbeat(new time_stamp()), - _check_interval(0), - _misses_allowed(0), - _misses(0) -{ *this = to_copy; } - -heartbeat::~heartbeat() { WHACK(_next_heartbeat); } - -time_stamp heartbeat::heartbeat_time() const { return *_next_heartbeat; } - -bool heartbeat::due() const { return time_left() <= 0; } - -void heartbeat::made_request() { _misses++; reset_next_beat(); } - -void heartbeat::kabump() { _misses = 0; reset_next_beat(); } - -void heartbeat::reset_next_beat() -{ *_next_heartbeat = time_stamp(_check_interval); } - -int heartbeat::time_left() const -{ return int(_next_heartbeat->value() - time_stamp().value()); } - -bool heartbeat::dead() const -{ - // two cases mean the timer's dead; (1) if the misses are already too high, - // or (2) if the heartbeat is due and the misses are as many as allowed. - return (_misses > _misses_allowed) - || (due() && (_misses >= _misses_allowed)); -} - -void heartbeat::reset(int misses_allowed, int check_interval) -{ - _misses_allowed = misses_allowed; - _misses = 0; - _check_interval = check_interval; - reset_next_beat(); -} - -astring heartbeat::text_form(bool detailed) const -{ - astring to_return = (dead()? astring("expired, ") : astring("alive, ")); - to_return += (!dead() && due() ? astring("due now, ") - : astring::empty_string()); - to_return += a_sprintf("beats left=%d", misses_left()); - if (detailed) { - to_return += a_sprintf(", missed=%d, interval=%d, ", - missed_so_far(), checking_interval()); - to_return += astring("next=") + heartbeat_time().text_form(); - } - return to_return; -} - -heartbeat &heartbeat::operator =(const heartbeat &to_copy) -{ - if (this == &to_copy) return *this; - _check_interval = to_copy._check_interval; - _misses_allowed = to_copy._misses_allowed; - _misses = to_copy._misses; - *_next_heartbeat = *to_copy._next_heartbeat; - return *this; -} - -} //namespace. - - diff --git a/core/library/processes/heartbeat.h b/core/library/processes/heartbeat.h deleted file mode 100644 index 19fe3b32..00000000 --- a/core/library/processes/heartbeat.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef HEARTBEAT_CLASS -#define HEARTBEAT_CLASS - -/*****************************************************************************\ -* * -* Name : heartbeat * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace processes { - -//! Monitors a periodic heartbeat to track a resource's health. -/*! - The heartbeat is defined as a "request-and-response" based check; when the - next periodic heartbeat is due, a request is sent out. The heartbeat - request is considered successfully dealt with only if a response comes back - for the request. If the user-defined number of requests are sent without - a single response coming back, then the 'patient' is considered dead. -*/ - -class heartbeat : public virtual basis::root_object -{ -public: - heartbeat(int misses_allowed = 500, int check_interval = 10000); - //!< creates a heartbeat monitor with the specified interval and maximum skips permitted. - /*!< this allows the heartbeat request to be missed "misses_allowed" times. - the heartbeats will become due every "check_interval" milliseconds. the - defaults are a joke; you really need to set them. */ - heartbeat(const heartbeat &to_copy); - - ~heartbeat(); - - DEFINE_CLASS_NAME("heartbeat"); - - heartbeat &operator =(const heartbeat &to_copy); - - basis::astring text_form(bool detailed = false) const; - //!< returns a readable form of the heartbeat's information. - - void reset(int misses_allowed, int check_interval); - //!< retrains the heartbeat monitor for a new configuration. - - bool due() const; - //!< is the next heartbeat due yet? - - bool dead() const; - //!< is this object considered dead from missing too many heartbeats? - /*!< this is true if the heartbeat being monitored missed too many - responses to heartbeat requests. if the maximum allowed requests have - been made and there was not even a single response, then the object is - considered dead. */ - - void made_request(); - //!< records that another heartbeat request was sent out. - /*!< the time for the next heartbeat request is reset to the time between - beats. if there were already the maximum allowed number of missed - responses, then the object is now dead. */ - void need_beat() { made_request(); } - //!< a synonym for the made_request() method. - - void kabump(); - //!< registers a heartbeat response and sets the state to healthy. - /*!< this records that a heartbeat response came back from the monitored - object. after this call, there are no heartbeats recorded as missed at - all. */ - void recycle() { kabump(); } - //!< a synonym for kabump(). - - // reporting functions for internal state... - - int missed_so_far() const { return _misses; } - //!< returns the number of heartbeat responses that are pending. - int misses_left() const { return _misses_allowed - _misses; } - //!< the number of misses that this object is still allowed. - - int allowed_misses() const { return _misses_allowed; } - //!< returns the number of misses allowed overall. - int checking_interval() const { return _check_interval; } - //!< returns the period of the heartbeats. - - timely::time_stamp heartbeat_time() const; - //!< returns the time when the next heartbeat will be requested. - /*!< if no heartbeats had been missed yet, then this is the time when - the due() method starts returning true. */ - - int time_left() const; - //!< number of milliseconds left before the next beat will be requested. - /*!< if the number is zero or negative, then a heartbeat is due. */ - -private: - timely::time_stamp *_next_heartbeat; - int _check_interval; - int _misses_allowed; - int _misses; - - void reset_next_beat(); //!< resets the next_heartbeat to our interval. -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/launch_process.cpp b/core/library/processes/launch_process.cpp deleted file mode 100644 index bb8d7ada..00000000 --- a/core/library/processes/launch_process.cpp +++ /dev/null @@ -1,345 +0,0 @@ - -// Name : launch_process -// Author : Chris Koeritz -/****************************************************************************** -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "launch_process.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef __UNIX__ - #include - #include - #include - #include -#endif -#ifdef __WIN32__ - #include - #include - #include -#endif - -//#define DEBUG_LAUNCH_PROCESS - // uncomment for noisier debugging info. - -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace structures; -using namespace timely; - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s); - -namespace processes { - -//hmmm: some of these should probably be safe statics. - -mutex &__process_synchronizer() { - static mutex __hidden_synch; - return __hidden_synch; -} - -int_set __our_kids() { - static int_set __hidden_kids; - return __hidden_kids; -} - -#ifdef __WIN32__ -bool launch_process::event_poll(MSG &message) -{ - message.hwnd = 0; - message.message = 0; - message.wParam = 0; - message.lParam = 0; - if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE)) - return false; - TranslateMessage(&message); - DispatchMessage(&message); - return true; -} -#endif - -#define SUPPORT_SHELL_EXECUTE - // if this is not commented out, then the ShellExecute version of launch_ - // -process() is available. when commented out, ShellExecute is turned off. - // disabling this support is the most common because the ShellExecute method - // in win32 was only supported for wk203 and wxp, that is only after - // windows2000 was already available. since nt and w2k don't support this, - // we just usually don't mess with it. it didn't answer a single one of our - // issues on windows vista (wfista) anyway, so it's not helping. - -//const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; - // maximum command line that we'll deal with here. - -#ifdef __UNIX__ -void launch_process::exiting_child_signal_handler(int sig_num) -{ - FUNCDEF("exiting_child_signal_handler"); - if (sig_num != SIGCHLD) { - // uhhh, this seems wrong. - } - auto_synchronizer l(__process_synchronizer()); - for (int i = 0; i < __our_kids().length(); i++) { - int status; - pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG); - if ( (exited == -1) || (exited == __our_kids()[i]) ) { - // negative one denotes an error, which we are going to assume means the - // process has exited via some other method than our wait. if the value - // is the same as the process we waited for, that means it exited. - __our_kids().zap(i, i); - i--; - } else if (exited != 0) { - // zero would be okay; this result we do not understand. -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("unknown result %d waiting for process %d", exited, __our_kids()[i])); -#endif - } - } -} -#endif - -//hmmm: this doesn't seem to account for quoting properly at all? -char_star_array launch_process::break_line(astring &app, const astring ¶meters) -{ - FUNCDEF("break_line"); - char_star_array to_return; - int_array posns; - int num = 0; - // find the positions of the spaces and count them. - for (int j = 0; j < parameters.length(); j++) { - if (parameters[j] == ' ') { - num++; - posns += j; - } - } - // first, add the app name to the list of parms. - to_return += new char[app.length() + 1]; - app.stuff(to_return[0], app.length()); - int last_posn = 0; - // now add each space-separated parameter to the list. - for (int i = 0; i < num; i++) { - int len = posns[i] - last_posn; - to_return += new char[len + 1]; - parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len); - last_posn = posns[i] + 1; - } - // catch anything left after last separator. - if (last_posn < parameters.length() - 1) { - int len = parameters.length() - last_posn; - to_return += new char[len + 1]; - parameters.substring(last_posn, parameters.length() - 1) - .stuff(to_return[to_return.last()], len); - } - // add the sentinel to the list of strings. - to_return += NIL; -#ifdef DEBUG_LAUNCH_PROCESS - for (int q = 0; to_return[q]; q++) { - LOG(a_sprintf("%d: %s\n", q, to_return[q])); - } -#endif - // now a special detour; fix the app name to remove quotes, which are - // not friendly to pass to exec. - if (app[0] == '"') app.zap(0, 0); - if (app[app.end()] == '"') app.zap(app.end(), app.end()); - return to_return; -} - -basis::un_int launch_process::run(const astring &app_name_in, const astring &command_line, - int flag, basis::un_int &child_id) -{ -#ifdef DEBUG_LAUNCH_PROCESS - FUNCDEF("run"); -#endif - child_id = 0; - astring app_name = app_name_in; - if (app_name[0] != '"') - app_name.insert(0, "\""); - if (app_name[app_name.end()] != '"') - app_name += "\""; -#ifdef __UNIX__ - // unix / linux implementation. - if (flag & RETURN_IMMEDIATELY) { - // they want to get back right away. - pid_t kid_pid = fork(); -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("launch fork returned %d\n", kid_pid)); -#endif - if (!kid_pid) { - // this is the child; we now need to launch into what we were asked for. -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("process %d execing ", application_configuration::process_id()) + app_name - + " parms " + command_line + "\n"); -#endif - char_star_array parms = break_line(app_name, command_line); - execv(app_name.s(), parms.observe()); - // oops. failed to exec if we got to here. -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("child of fork (pid %d) failed to exec, error is ", - application_configuration::process_id()) - + critical_events::system_error_text(critical_events::system_error()) - + "\n"); -#endif - exit(0); // leave since this is a failed child process. - } else { - // this is the parent. let's see if the launch worked. - if (kid_pid == -1) { - // failure. - basis::un_int to_return = critical_events::system_error(); -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("parent %d is returning after failing to create, " - "error is ", application_configuration::process_id()) - + critical_events::system_error_text(to_return) - + "\n"); -#endif - return to_return; - } else { - // yes, launch worked okay. - child_id = kid_pid; - { - auto_synchronizer l(__process_synchronizer()); - __our_kids() += kid_pid; - } - // hook in our child exit signal handler. - signal(SIGCHLD, exiting_child_signal_handler); - -#ifdef DEBUG_LAUNCH_PROCESS - LOG(a_sprintf("parent %d is returning after successfully " - "creating %d ", application_configuration::process_id(), kid_pid) + app_name - + " parms " + command_line + "\n"); -#endif - return 0; - } - } - } else { - // assume they want to wait. - return system((app_name + " " + command_line).s()); - } -#elif defined(__WIN32__) - -//checking on whether we have admin rights for the launch. -//if (IsUserAnAdmin()) { -// MessageBox(0, (astring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); -//} else { -// MessageBox(0, (astring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); -//} - - PROCESS_INFORMATION process_info; - ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); - -#ifdef SUPPORT_SHELL_EXECUTE - if (flag & SHELL_EXECUTE) { - // new code for using shell execute method--required on vista for proper - // launching with the right security tokens. - int show_cmd = 0; - if (flag & HIDE_APP_WINDOW) { - // magic that hides a console window for mswindows. - show_cmd = SW_HIDE; - } else { - show_cmd = SW_SHOWNORMAL; - } - - SHELLEXECUTEINFO exec_info; - ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO)); - exec_info.cbSize = sizeof(SHELLEXECUTEINFO); - exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info. - | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs. - exec_info.hwnd = GetDesktopWindow(); -//hmmm: is get desktop window always appropriate? - to_unicode_persist(temp_verb, "open"); -//does "runas" work on xp also? or anywhere? - exec_info.lpVerb = temp_verb; - to_unicode_persist(temp_file, app_name); - exec_info.lpFile = temp_file; - to_unicode_persist(temp_parms, command_line); - exec_info.lpParameters = temp_parms; - exec_info.nShow = show_cmd; -// exec_info.hProcess = &process_info; - - BOOL worked = ShellExecuteEx(&exec_info); - if (!worked) - return critical_events::system_error(); - // copy out the returned process handle. - process_info.hProcess = exec_info.hProcess; - process_info.dwProcessId = GetProcessId(exec_info.hProcess); - } else { -#endif //shell exec - // standard windows implementation using CreateProcess. - STARTUPINFO startup_info; - ZeroMemory(&startup_info, sizeof(STARTUPINFO)); - startup_info.cb = sizeof(STARTUPINFO); - int create_flag = 0; - if (flag & HIDE_APP_WINDOW) { - // magic that hides a console window for mswindows. -// version ver = portable::get_OS_version(); -// version vista_version(6, 0); -// if (ver < vista_version) { -// // we suspect that this flag is hosing us in vista. - create_flag = CREATE_NO_WINDOW; -// } - } - astring parms = app_name + " " + command_line; - bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false, - create_flag, NIL, NIL, &startup_info, &process_info); - if (!success) - return critical_events::system_error(); - // success then, merge back into stream. - -#ifdef SUPPORT_SHELL_EXECUTE - } -#endif //shell exec - - // common handling for CreateProcess and ShellExecuteEx. - child_id = process_info.dwProcessId; - basis::un_long retval = 0; - if (flag & AWAIT_VIA_POLLING) { - // this type of waiting is done without blocking on the process. - while (true) { - MSG msg; - event_poll(msg); - // check if the process is gone yet. - BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval); - if (!ret) { - break; - } else { - // if they aren't saying it's still active, then we will leave. - if (retval != STILL_ACTIVE) - break; - } - time_control::sleep_ms(14); - } - } else if (flag & AWAIT_APP_EXIT) { - // they want to wait for the process to exit. - WaitForInputIdle(process_info.hProcess, INFINITE); - WaitForSingleObject(process_info.hProcess, INFINITE); - GetExitCodeProcess(process_info.hProcess, &retval); - } - // drop the process and thread handles. - if (process_info.hProcess) - CloseHandle(process_info.hProcess); - if (process_info.hThread) - CloseHandle(process_info.hThread); - return (basis::un_int)retval; -#else - #pragma error("hmmm: launch_process: no implementation for this OS.") -#endif - return 0; -} - -} // namespace. - diff --git a/core/library/processes/launch_process.h b/core/library/processes/launch_process.h deleted file mode 100644 index a988c4c5..00000000 --- a/core/library/processes/launch_process.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef LAUNCH_PROCESS_CLASS -#define LAUNCH_PROCESS_CLASS - -/*****************************************************************************\ -* * -* Name : launch_process -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -// forward. -struct tagMSG; - -namespace processes { - -//! a simple wrapper of an array of char *, used by launch_process::break_line(). -class char_star_array : public basis::array -{ -public: - char_star_array() : basis::array(0, NIL, SIMPLE_COPY | EXPONE | FLUSH_INVISIBLE) {} - ~char_star_array() { - // clean up all the memory we're holding. - for (int i = 0; i < length(); i++) { - delete [] (use(i)); - } - } -}; - -////////////// - -//! Provides the capability to start processes in a variety of ways to run other applications. - -class launch_process : public virtual basis::nameable -{ -public: - DEFINE_CLASS_NAME("launch_process"); - - virtual ~launch_process() {} - - enum launch_flags { - HIDE_APP_WINDOW = 0x1, - //!< launches the application invisibly if possible. - AWAIT_APP_EXIT = 0x2, - //!< stays in the function until the launched application has exited. - RETURN_IMMEDIATELY = 0x4, - //!< starts the application and comes right back to the caller. - AWAIT_VIA_POLLING = 0x8, - //!< launches the app but polls and doesn't block on its exit. - SHELL_EXECUTE = 0x10 - //!< only valid on windows--uses ShellExecute instead of CreateProcess. - }; - - static basis::un_int run(const basis::astring &app_name, const basis::astring &command_line, - int flag, basis::un_int &child_id); - //!< starts an application using the "app_name" as the executable to run. - /*!< the "command_line" is the set of parameters to be passed to the app. - the return value is OS specific but can be identified using - system_error_text(). usually a zero return means success and non-zero - codes are errors. the "flag" is an XORed value from the process launch - flags that dictates how the app is to be started. in practice, only the - HIDE_APP_WINDOW flag can be combined with other values. if either AWAIT - flag is used, then the return value will be the launched process's own - exit value. the thread or process_id of the launched process is stored - in "child_id" if appropriate. */ - - static char_star_array break_line(basis::astring &app, const basis::astring ¶meters); - //!< prepares an "app" to launch with the "parameters" (via exec). - /*!< this breaks the strings for an application named "app" and its - "parameters" into an array of char * that is appropriate for the execv - function. */ - -private: -#ifdef __UNIX__ - static void exiting_child_signal_handler(int sig_num); - //!< awaits the child processes rather than leaving process handles willy nilly. -#endif -#ifdef __WIN32__ - static bool event_poll(tagMSG &message); - //!< tries to process one win32 event and retrieve the "message" from it. - /*!< this is a very general poll and will retrieve any message that's - available for the current thread. the message is actually processed - here also, by calling translate and dispatch. the returned structure - is mainly interesting for knowing what was done. */ -#endif - -}; - -} // namespace. - -#endif // outer guard. - diff --git a/core/library/processes/letter.cpp b/core/library/processes/letter.cpp deleted file mode 100644 index d197aa19..00000000 --- a/core/library/processes/letter.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/*****************************************************************************\ -* * -* Name : letter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "letter.h" - -#include -#include -#include - -using namespace basis; -using namespace timely; - -namespace processes { - -letter::letter(int type, int start_after) -: _type(type), - _ready_time(new time_stamp(start_after)) -{} - -letter::letter(const letter &to_copy) -: _type(to_copy._type), - _ready_time(new time_stamp(*to_copy._ready_time)) -{ -} - -letter::~letter() -{ - _type = 0; - WHACK(_ready_time); -} - -bool letter::ready_to_send() { return time_stamp() >= *_ready_time; } - -void letter::set_ready_time(int start_after) -{ *_ready_time = time_stamp(start_after); } - -letter &letter::operator =(const letter &to_copy) -{ - if (this == &to_copy) return *this; - _type = to_copy._type; - *_ready_time = *to_copy._ready_time; - return *this; -} - -} //namespace. - diff --git a/core/library/processes/letter.h b/core/library/processes/letter.h deleted file mode 100644 index 50c77dfb..00000000 --- a/core/library/processes/letter.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef LETTER_CLASS -#define LETTER_CLASS - -/*****************************************************************************\ -* * -* Name : letter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace processes { - -//! A virtual base class for pieces of "mail". Used by the mailbox object. - -class letter : public virtual basis::text_formable -{ -public: - letter(int type = 0, int start_after = 0); - //!< constructs a letter with the "type" and initial pause of "start_after". - /*!< a "type" for this letter must be specified, even if it is not intended - to be used. some letter managers may rely on this number identifying - different kinds of mail. the types should be unique within one mailbox. - a "type" of zero indicates an invalid letter. if the "start_after" is - non-zero, then it indicates that this letter should not be sent until - that many milliseconds have elapsed. */ - - letter(const letter &to_copy); - //!< copy constructor for base parts. - - virtual ~letter(); - //!< derived classes should also implement this. - /*!< a virtual destructor should be implemented by each derived class - to take care of class specific cleaning. note that the destructor should - NEVER attempt to use the mailbox system that it was stored in (or any - other mailbox system for that matter). this is necessary for prohibiting - deadlock conditions, but it's not that much of a restriction usually. */ - - letter &operator =(const letter &to_copy); - //!< assignment operator for base object. - - virtual void text_form(basis::base_string &fill) const = 0; - //!< derived letters must print a status blurb describing their contents. - - int type() const { return _type; } - //!< returns the type of letter held here. - - bool ready_to_send(); - //!< returns true if this letter is ready to - - void set_ready_time(int start_after); - //!< resets the time when this letter is ready to be sent. - /*!< the letter will now not be allowed to send until "start_after" - milliseconds from now. once the letter is added to a mailbox, it may - be too late to adjust this duration. */ - -private: - int _type; //!< the kind of mail this item represents. - timely::time_stamp *_ready_time; //!< time when this letter will be ready to send. -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/mail_stop.h b/core/library/processes/mail_stop.h deleted file mode 100644 index 12afe524..00000000 --- a/core/library/processes/mail_stop.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef MAIL_STOP_CLASS -#define MAIL_STOP_CLASS - -/*****************************************************************************\ -* * -* Name : mail_stop * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "safe_callback.h" - -#include - -namespace processes { - -// forward: -class letter; - -//! Base class for routes on which letters are automatically delivered. -/*! - The letters will show up for a particular unique id at this mail stop. - They are delivered by the object serving as the post office. -*/ - -class mail_stop : public safe_callback -{ -public: - - // it is required that the derived mail_stop invoke the end_availability() - // method in its destructor before it destroys any other objects. - - class items_to_deliver : public callback_data_block { - public: - items_to_deliver(const structures::unique_int &id, letter *package) - : _id(id), _package(package) {} - const structures::unique_int &_id; - letter *_package; - }; - - virtual void delivery_for_you(const structures::unique_int &id, letter *package) = 0; - //!< the derived object must provide this function. - /*!< prompts the recipient with the "id" to accept delivery of a "package". - the package can be modified as desired and MUST be recycled before - returning from this function. - IMPORTANT NOTE: the receiver MUST be thread-safe with respect to the - objects that it uses to handle this delivery! this is because mail is - delivered on a thread other than the main program thread. */ - -protected: - //! invoked by the safe callback machinery. - /*! this is implemented in this class and merely re-routes the call to the - more specific delivery_for_you() method. */ - virtual void real_callback(callback_data_block &data) { - items_to_deliver *bits = dynamic_cast(&data); - if (!bits) return; // bad type. - delivery_for_you(bits->_id, bits->_package); - } - -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/mailbox.cpp b/core/library/processes/mailbox.cpp deleted file mode 100644 index 52b89adf..00000000 --- a/core/library/processes/mailbox.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/*****************************************************************************\ -* * -* Name : mailbox * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "letter.h" -#include "mailbox.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace loggers; -using namespace structures; -using namespace textual; - -namespace processes { - -const int MAILBOX_BITS = 9; - // we allow N bits in our table size, which means the table will have 2^N - // elements. careful with that increase... - -class mail_cabinet -{ -public: - amorph _waiting; - - mail_cabinet() : _waiting(0) {} - - ~mail_cabinet() { _waiting.reset(); } - - mail_cabinet(mail_cabinet &formal(to_copy)) { - non_continuable_error("mail_cabinet", "copy constructor", "should never be called"); - } - - mail_cabinet &operator =(mail_cabinet &formal(to_copy)) { - non_continuable_error("mail_cabinet", "assignment operator", - "should never be called"); - return *this; - } -}; - -////////////// - -class mailbox_bank : public int_hash -{ -public: - mailbox_bank() : int_hash (MAILBOX_BITS) {} - ~mailbox_bank() { reset(); } - - void get_ids(int_set &to_fill); - // returns the list of identifiers for people with mailboxes. - - void add_cabinet(const unique_int &id); - // creates a new mail receptacle for the "id". - - bool zap_cabinet(const unique_int &id); - // removes the cabinet for "id". - - void add_item(const unique_int &id, letter *to_add); - // stuffs an item "to_add" in for "id". - - bool get(const unique_int &id, letter * &to_receive); - // retrieves the next waiting package for "id" into "to_receive". - - void clean_up(); - // gets rid of any cabinets without any packages. -}; - -void mailbox_bank::clean_up() -{ - int_set ids; - get_ids(ids); - for (int i = 0; i < ids.elements(); i++) { - mail_cabinet *entry = find(ids[i]); - // if the cabinet has zero elements, we zap it. - if (!entry->_waiting.elements()) zap(ids[i]); - } -} - -void mailbox_bank::get_ids(int_set &to_fill) { to_fill = ids(); } - -void mailbox_bank::add_cabinet(const unique_int &id) -{ - if (find(id.raw_id())) return; // already exists. - mail_cabinet *to_add = new mail_cabinet; - add(id.raw_id(), to_add); -} - -bool mailbox_bank::zap_cabinet(const unique_int &id) -{ - if (!find(id.raw_id())) return false; // doesn't exist. - return zap(id.raw_id()); -} - -void mailbox_bank::add_item(const unique_int &id, letter *to_add) -{ - mail_cabinet *found = find(id.raw_id()); - if (!found) { - add_cabinet(id); - found = find(id.raw_id()); - // there should never be a failure case that would prevent the new cabinet - // from being added (besides overall memory failure). - if (!found) { -//complain - return; - } - } - found->_waiting.append(to_add); -} - -bool mailbox_bank::get(const unique_int &id, letter * &to_receive) -{ - mail_cabinet *found = find(id.raw_id()); - if (!found) return false; // no cabinet, much less any mail. - - if (!found->_waiting.elements()) return false; // no mail waiting. - for (int i = 0; i < found->_waiting.elements(); i++) { - // check if its time is ripe... - if (!found->_waiting.borrow(i)->ready_to_send()) continue; - // get the waiting mail and remove its old slot. - to_receive = found->_waiting.acquire(i); - found->_waiting.zap(i, i); - return true; - } - return false; -} - -////////////// - -mailbox::mailbox() -: _transaction_lock(new mutex), - _packages(new mailbox_bank) -{ -} - -mailbox::~mailbox() -{ - WHACK(_packages); - WHACK(_transaction_lock); -} - -void mailbox::get_ids(int_set &to_fill) -{ - auto_synchronizer l(*_transaction_lock); - _packages->get_ids(to_fill); -} - -void mailbox::drop_off(const unique_int &id, letter *package) -{ - auto_synchronizer l(*_transaction_lock); - _packages->add_item(id, package); -} - -void mailbox::clean_up() -{ - auto_synchronizer l(*_transaction_lock); - _packages->clean_up(); -} - -int mailbox::waiting(const unique_int &id) const -{ - auto_synchronizer l(*_transaction_lock); - mail_cabinet *found = _packages->find(id.raw_id()); - int to_return = 0; // if no cabinet, this is the proper count. - // if there is a cabinet, then get the size. - if (found) - to_return = found->_waiting.elements(); - return to_return; -} - -bool mailbox::pick_up(const unique_int &id, letter * &package) -{ - package = NIL; - auto_synchronizer l(*_transaction_lock); - return _packages->get(id, package); -} - -bool mailbox::close_out(const unique_int &id) -{ - auto_synchronizer l(*_transaction_lock); - bool ret = _packages->zap_cabinet(id); - return ret; -} - -void mailbox::show(astring &to_fill) -{ - auto_synchronizer l(*_transaction_lock); - int_set ids; - _packages->get_ids(ids); - for (int i = 0; i < ids.elements(); i++) { - mail_cabinet &mc = *_packages->find(ids[i]); - to_fill += astring(astring::SPRINTF, "cabinet %d:", ids[i]) - + parser_bits::platform_eol_to_chars(); - for (int j = 0; j < mc._waiting.elements(); j++) { - letter &l = *mc._waiting.borrow(j); - astring text; - l.text_form(text); - to_fill += string_manipulation::indentation(4) - + astring(astring::SPRINTF, "%4ld: ", j + 1) - + text + parser_bits::platform_eol_to_chars(); - } - } -} - -void mailbox::limit_boxes(int max_letters) -{ - auto_synchronizer l(*_transaction_lock); - int_set ids; - _packages->get_ids(ids); - for (int i = 0; i < ids.elements(); i++) { - mail_cabinet &mc = *_packages->find(ids[i]); - if (mc._waiting.elements() > max_letters) { - // this one needs cleaning. - mc._waiting.zap(max_letters, mc._waiting.elements() - 1); - } - } -} - -void mailbox::apply(apply_function *to_apply, void *data_link) -{ - auto_synchronizer l(*_transaction_lock); - int_set ids; - _packages->get_ids(ids); - for (int i = 0; i < ids.elements(); i++) { - mail_cabinet &mc = *_packages->find(ids[i]); - for (int j = 0; j < mc._waiting.elements(); j++) { - letter &l = *mc._waiting.borrow(j); - outcome ret = to_apply(l, ids[i], data_link); - if ( (ret == APPLY_WHACK) || (ret == APPLY_WHACK_STOP) ) { - // they wanted this node removed. - mc._waiting.zap(j, j); - j--; // skip back before missing guy so we don't omit anyone. - if (ret == APPLY_WHACK_STOP) - break; // they wanted to be done with it also. - } else if (ret == APPLY_STOP) { - break; // we hit the exit condition. - } - } - } -} - -} //namespace. - - diff --git a/core/library/processes/mailbox.h b/core/library/processes/mailbox.h deleted file mode 100644 index e41fe946..00000000 --- a/core/library/processes/mailbox.h +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef MAILBOX_CLASS -#define MAILBOX_CLASS - -/*****************************************************************************\ -* * -* Name : mailbox * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace processes { - -class letter; -class mailbox_bank; - -//! Implements a thread safe "mail" delivery system. -/*! - Senders can drop packages off into the mailbox and the receivers can get - those packages back out of it. The base class for all mail items is also - provided in this library (letter.h). The name of this object is slightly - misleading; this object is really more of a post office. Each unique id - has its own mailbox slot for receiving mail. -*/ - -class mailbox : public virtual basis::root_object -{ -public: - mailbox(); - virtual ~mailbox(); - - void drop_off(const structures::unique_int &id, letter *package); - //!< drops a "package" in the mailbox for "id". - /*!< note that when you send a package to someone, you give up all - authority and control over that package. hopefully the end recipient - will eventually pick it up and then delete it. if the package is never - received, then this object will delete it. */ - - bool pick_up(const structures::unique_int &id, letter * &package); - //!< returns true if the mailbox for "id" had a "package" to be delivered. - /*!< don't forget to check multiple times on a true outcome, since there - could be more than one package waiting. false is returned when no more - mail is waiting. be careful; "package" could be a bomb. dynamic casts - seem appropriate as a method for ensuring that you get the type of - object you expect. note that once the invoker receives a package, it - is their responsibility to carefully manage it and then delete the - package after handling. not deleting the "package" pointer is grounds - for memory leaks. */ - - int waiting(const structures::unique_int &id) const; - //!< returns the number of items waiting for the "id" specified, if any. - - void get_ids(structures::int_set &to_fill); - //!< stuffs the set "to_fill" with the ids of all mailboxes present. - /*!< if you want only those mailboxes holding one or more letters, then - call the clean_up() method prior to this method. */ - - bool close_out(const structures::unique_int &id); - //!< dumps all packages stored for the "id" and shuts down its mailbox. - /*!< the destructors for those packages should never try to do anything - with the mailbox system or a deadlock could result. true is returned if - the "id" had a registered mailbox; false just indicates there was no box - to clear up. */ - - void show(basis::astring &to_fill); - //!< provides a picture of what's waiting in the mailbox. - /*!< this relies on the derived letter's required text_form() function. */ - - void clean_up(); - //!< removes any empty mailboxes from our list. - - void limit_boxes(int max_letters); - //!< establishes a limit on the number of letters. - /*!< this is a helper function for a very special mailbox; it has a - limited maximum size and any letters above the "max_letters" count will - be deleted. don't use this function on any mailbox where all letters - are important; your mailbox must have a notion of unreliability before - this would ever be appropriate. */ - - enum apply_outcomes { - OKAY = basis::common::OKAY, //!< continue apply process. - - DEFINE_OUTCOME(APPLY_STOP, -46, "Halt the apply process"), - DEFINE_OUTCOME(APPLY_WHACK, -47, "Removes the current letter, but " - "continues"), - DEFINE_OUTCOME(APPLY_WHACK_STOP, -48, "Halts apply and trashes the " - "current letter") - }; - - typedef basis::outcome apply_function(letter ¤t, int uid, void *data_link); - //!< the "apply_function" is what a user of apply() must provide. - /*!< the function will be called on every letter in the mailbox unless one - of the invocations returns APPLY_STOP or APPLY_WHACK_STOP; this causes - the apply process to stop (and zap the node for APPLY_WHACK). the "uid" - is the target for the "current" letter. the "data_link" provides a way - for the function to refer back to a parent class or data package of some - sort. note that all sorts of deadlocks will occur if your apply - function tries to do anything on the mailbox, even transitively. keep - those functions as simple as possible. */ - - void apply(apply_function *to_apply, void *data_link); - //!< calls the "to_apply" function on possibly every letter in the mailbox. - /*!< this iterates until the function returns a 'STOP' outcome. the - "data_link" pointer is passed to the apply function. NOTE: it is NOT safe - to rearrange or manipulate the mailbox in any way from your "to_apply" - function; the only changes allowed are those caused by the return value - from "to_apply". */ - -private: - basis::mutex *_transaction_lock; //!< keeps the state of the mailbox safe. - mailbox_bank *_packages; //!< the collection of mail that has arrived. - - // prohibited. - mailbox(const mailbox &); - mailbox &operator =(const mailbox &); -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/makefile b/core/library/processes/makefile deleted file mode 100644 index 57e23f6e..00000000 --- a/core/library/processes/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = processes -TYPE = library -TARGETS = processes.lib -SOURCE = configured_applications.cpp ethread.cpp heartbeat.cpp launch_process.cpp \ - letter.cpp mailbox.cpp post_office.cpp \ - process_control.cpp process_entry.cpp rendezvous.cpp safe_callback.cpp safe_roller.cpp \ - state_machine.cpp thread_cabinet.cpp - -include cpp/rules.def - diff --git a/core/library/processes/os_event.h b/core/library/processes/os_event.h deleted file mode 100644 index 9e52c388..00000000 --- a/core/library/processes/os_event.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef OS_EVENT_CLASS -#define OS_EVENT_CLASS - -/*****************************************************************************\ -* * -* Name : OS_event * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "letter.h" - -#include -#include - -namespace processes { - -// forward. -class post_office; - -//! Models an OS-level event so we can represent activities occurring there. - -class OS_event : public letter, public virtual basis::text_formable -{ -public: - basis::un_int _message; - basis::un_int _parm1; - basis::un_int _parm2; - - DEFINE_CLASS_NAME("OS_event"); - - OS_event(int event_type, basis::un_int message, basis::un_int parm1, basis::un_int parm2) - : letter(event_type), _message(message), _parm1(parm1), _parm2(parm2) {} - - virtual void text_form(basis::base_string &fill) const { - fill.assign(text_form()); - } - basis::astring text_form() const { - return basis::a_sprintf("os_event: msg=%d parm1=%d parm2=%d", _message, _parm1, _parm2); - } -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/post_office.cpp b/core/library/processes/post_office.cpp deleted file mode 100644 index 523e4bb3..00000000 --- a/core/library/processes/post_office.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/*****************************************************************************\ -* * -* Name : post_office * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ethread.h" -#include "letter.h" -#include "mailbox.h" -#include "post_office.h" -#include "thread_cabinet.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; - -namespace processes { - -//#define DEBUG_POST_OFFICE - // uncomment if you want the noisy version. - -#undef LOG -#define LOG(a) CLASS_EMERGENCY_LOG(program_wide_logger::get(), a) - -const int CLEANING_INTERVAL = 14 * SECOND_ms; - // the interval between cleaning of extra letters and dead mailboxes. - -const int SNOOZE_TIME_FOR_POSTMAN = 42; - // we'll snooze for this long if absolutely nothing happened during the - // thread's activation. if things are going on, our snooze time is reduced - // by the length of time we were delivering items. - -const int DELIVERIES_ALLOWED = 350; - // the maximum number of deliveries we'll try to get done per thread run. - -////////////// - -//hmmm: arrhhh--maybe we need to spawn a thread per postal route. - -class postal_carrier : public ethread -{ -public: - postal_carrier(post_office &parent, const unique_int &route) - : ethread(SNOOZE_TIME_FOR_POSTMAN, ethread::SLACK_INTERVAL), - _parent(parent), - _route(route) - {} - - DEFINE_CLASS_NAME("postal_carrier"); - - void perform_activity(void *) { - FUNCDEF("perform_activity"); - bool finished; - try { - finished = _parent.deliver_mail_on_route(_route, *this); - } catch(...) { - LOG(astring("caught exception during mail delivery!")); - } - if (!finished) { - // not finished delivering all items. - reschedule(); - } else { - reschedule(SNOOZE_TIME_FOR_POSTMAN); - } - } - -private: - post_office &_parent; - unique_int _route; -}; - -////////////// - -class postal_cache : public mailbox {}; - -////////////// - -class tagged_mail_stop : public virtual text_formable -{ -public: - mail_stop *_route; - unique_int _thread_id; - unique_int _id; - - tagged_mail_stop(const unique_int &id = 0, mail_stop *route = NIL, - const unique_int &thread_id = 0) - : _route(route), _thread_id(thread_id), _id(id) {} - - DEFINE_CLASS_NAME("tagged_mail_stop"); - - virtual void text_form(basis::base_string &fill) const { - fill.assign(text_form()); - } - - virtual astring text_form() const { - return a_sprintf("%s: id=%d, addr=%08lx, thr_id=%d", - static_class_name(), _id.raw_id(), _route, _thread_id.raw_id()); - } -}; - -////////////// - -class route_map : public amorph -{ -public: - tagged_mail_stop *find(const unique_int &id) { - for (int i = 0; i < elements(); i++) { - tagged_mail_stop *curr = borrow(i); - if (curr && (curr->_id == id)) return curr; - } - return NIL; - } - - bool zap(const unique_int &id) { - for (int i = 0; i < elements(); i++) { - tagged_mail_stop *curr = borrow(i); - if (curr && (curr->_id == id)) { - amorph::zap(i, i); - return true; - } - } - return false; - } - -}; - -////////////// - -class letter_morph : public amorph {}; - -////////////// - -post_office::post_office() -: _post(new mailbox), - _routes(new route_map), - _next_cleaning(new time_stamp), - _threads(new thread_cabinet) -{ -} - -post_office::~post_office() -{ - stop_serving(); - WHACK(_post); - WHACK(_routes); - WHACK(_next_cleaning); - WHACK(_threads); -} - -void post_office::show_routes(astring &to_fill) -{ - auto_synchronizer l(c_mutt); -//hmmm: simplify this; just use the int_set returning func and print that. - astring current_line; - astring temp; - if (_routes->elements()) - to_fill += astring("Mail Delivery Routes:") + parser_bits::platform_eol_to_chars(); - - for (int i = 0; i < _routes->elements(); i++) { - const tagged_mail_stop *tag = _routes->get(i); - if (!tag) continue; - temp = astring(astring::SPRINTF, "%d ", tag->_id.raw_id()); - if (current_line.length() + temp.length() >= 80) { - current_line += parser_bits::platform_eol_to_chars(); - to_fill += current_line; - current_line.reset(); - } - current_line += temp; - } - // catch the last line we created. - if (!!current_line) to_fill += current_line; -} - -void post_office::stop_serving() { if (_threads) _threads->stop_all(); } - -void post_office::show_mail(astring &output) -{ - output.reset(); - output += parser_bits::platform_eol_to_chars(); - output += astring("Mailbox Contents at ") + time_stamp::notarize(true) - + parser_bits::platform_eol_to_chars() + parser_bits::platform_eol_to_chars(); - astring box_state; - _post->show(box_state); - if (box_state.t()) output += box_state; - else - output += astring("No items are awaiting delivery.") - + parser_bits::platform_eol_to_chars(); -} - -void post_office::drop_off(const unique_int &id, letter *package) -{ -#ifdef DEBUG_POST_OFFICE - FUNCDEF("drop_off"); - LOG(astring(astring::SPRINTF, "mailbox drop for %d: ", id) - + package->text_form()); -#endif - _post->drop_off(id, package); -#ifdef DEBUG_POST_OFFICE - if (!route_listed(id)) { - LOG(a_sprintf("letter for %d has no route!", id)); - } -#endif -} - -bool post_office::pick_up(const unique_int &id, letter * &package) -{ -#ifdef DEBUG_POST_OFFICE - FUNCDEF("pick_up"); -#endif - bool to_return = _post->pick_up(id, package); -#ifdef DEBUG_POST_OFFICE - if (to_return) - LOG(astring(astring::SPRINTF, "mailbox grab for %d: ", id) - + package->text_form()); -#endif - return to_return; -} - -bool post_office::route_listed(const unique_int &id) -{ - int_set route_set; - get_route_list(route_set); - return route_set.member(id.raw_id()); -} - -void post_office::get_route_list(int_set &route_set) -{ - auto_synchronizer l(c_mutt); - - // gather the set of routes that we should carry mail to. - route_set.reset(); - - if (!_routes->elements()) { - // if there are no elements, why bother iterating? - return; - } - - for (int i = 0; i < _routes->elements(); i++) { - const tagged_mail_stop *tag = _routes->get(i); - if (!tag) continue; - route_set.add(tag->_id.raw_id()); - } -} - -void post_office::clean_package_list(post_office &formal(post), - letter_morph &to_clean) -{ - FUNCDEF("clean_package_list"); - auto_synchronizer l(c_mutt); - - // recycle all the stuff we had in the list. - while (to_clean.elements()) { - letter *package = to_clean.acquire(0); - to_clean.zap(0, 0); - if (!package) { - LOG("saw empty package in list to clean!"); - continue; - } - WHACK(package); - } -} - -bool post_office::deliver_mail_on_route(const unique_int &route, - ethread &carrier) -{ - FUNCDEF("deliver_mail_on_route"); - auto_synchronizer l(c_mutt); - -#ifdef DEBUG_POST_OFFICE - time_stamp enter; -#endif - if (carrier.should_stop()) return true; // get out if thread was told to. - - int deliveries = 0; // number of items delivered so far. - letter_morph items_for_route; - // holds the items that need to be sent to this route. - - // pickup all of the mail that we can for this route. - while (deliveries < DELIVERIES_ALLOWED) { - if (carrier.should_stop()) - return true; // get out if thread was told to. - letter *package; - if (!_post->pick_up(route, package)) { - // there are no more letters for this route. - break; // skip out of the loop. - } - deliveries++; // count this item as a delivery. - items_for_route.append(package); - } - - if (!items_for_route.elements()) return true; // nothing to handle. - - // locate the destination for this route. - tagged_mail_stop *real_route = _routes->find(route); // find the route. - if (!real_route) { - // we failed to find the route we wanted... - LOG(astring(astring::SPRINTF, "route %d disappeared!", route.raw_id())); - clean_package_list(*this, items_for_route); - return true; - } - - // now deliver what we found for this route. - for (int t = 0; t < items_for_route.elements(); t++) { - if (carrier.should_stop()) { - // get out if thread was told to. - return true; - } - letter *package = items_for_route.acquire(t); - // hand the package out on the route. - mail_stop::items_to_deliver pack(route, package); - real_route->_route->invoke_callback(pack); - // the callee is responsible for cleaning up. - } - - bool finished_all = (deliveries < DELIVERIES_ALLOWED); - // true if we handled everything we could have. - - if (carrier.should_stop()) return true; // get out if thread was told to. - - // this bit is for the post office at large, but we don't want an extra - // thread when we've got all these others handy. - bool cleaning_time = time_stamp() > *_next_cleaning; - if (cleaning_time) { - _post->clean_up(); // get rid of dead mailboxes in main post office. - _next_cleaning->reset(CLEANING_INTERVAL); - } - - time_stamp exit; -#ifdef DEBUG_POST_OFFICE - int duration = int(exit.value() - enter.value()); - if (duration > 20) - LOG(a_sprintf("deliveries took %d ms.", duration)); -#endif - return finished_all; -} - -bool post_office::register_route(const unique_int &id, - mail_stop &carrier_path) -{ - auto_synchronizer l(c_mutt); - - tagged_mail_stop *found = _routes->find(id); - if (found) return false; // already exists. - - postal_carrier *po = new postal_carrier(*this, id); - unique_int thread_id = _threads->add_thread(po, false, NIL); - // add the thread so we can record its id. - tagged_mail_stop *new_stop = new tagged_mail_stop(id, &carrier_path, - thread_id); - _routes->append(new_stop); - // add the mail stop to our listings. - po->start(NIL); - // now start the thread so it can begin cranking. - return true; -} - -bool post_office::unregister_route(const unique_int &id) -{ - auto_synchronizer l(c_mutt); - - tagged_mail_stop *tag = _routes->find(id); - if (!tag) return false; // doesn't exist yet. - unique_int thread_id = tag->_id; - _routes->zap(id); - _threads->zap_thread(thread_id); - return true; -} - -} //namespace. - - diff --git a/core/library/processes/post_office.h b/core/library/processes/post_office.h deleted file mode 100644 index da53e6a1..00000000 --- a/core/library/processes/post_office.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef CENTRAL_MAILBOX_CLASS -#define CENTRAL_MAILBOX_CLASS - -/*****************************************************************************\ -* * -* Name : post_office * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "mail_stop.h" - -#include -#include - -namespace processes { - -class letter; -class letter_morph; -class mailbox; -class postal_cache; -class route_map; -class thread_cabinet; - -//! Manages a collection of mailboxes and implements delivery routes for mail. - -class post_office -{ -public: - post_office(); - - virtual ~post_office(); - //!< stop_serving must be invoked prior to this destructor. - - void stop_serving(); - //!< gets the mailbox to stop delivering items prior to a shutdown. - - // informational functions... - - DEFINE_CLASS_NAME("post_office"); - - void show_mail(basis::astring &to_fill); - //!< prints a snapshot of all currently pending letters into "to_fill". - - void show_routes(basis::astring &to_fill); - //!< writes a listing of the current routes into "to_fill". - - // general delivery services subject to the constraints of the mailbox class. - - void drop_off(const structures::unique_int &id, letter *package); - //!< sends a "package" on its way to the "id" via the registered route. - /*!< note that mail is not rejected if there is no known route to the - mail_stop for the "id"; it is assumed in that case that the recipient - will check at the post office. */ - - bool pick_up(const structures::unique_int &id, letter * &package); - //!< retrieves a "package" intended for the "id" if one exists. - /*!< false is returned if none are available. on success, the "package" is - filled in with the address of the package and it is the caller's - responsibility to destroy or recycle() it after dealing with it. */ - - ////////////// - - // mail delivery and routing support. - - bool register_route(const structures::unique_int &id, mail_stop &carrier_path); - //!< registers a route "carrier_path" for mail deliveries to the "id". - - bool unregister_route(const structures::unique_int &id); - //!< removes a route for the "id". - /*!< this should be done before the object's destructor is invoked since - the letter carrier could be on his way with a letter at an arbitrary time. - also, the mail_stop should be shut down (with end_availability()) at that - time also. if those steps are taken, then the carrier is guaranteed not - to bother the recipient. */ - - bool route_listed(const structures::unique_int &id); - //!< returns true if there is a route listed for the "id". - /*!< this could change at any moment, since another place in the source - code could remove the route just after this call. it is information from - the past by the time it's returned. */ - - ////////////// - - bool deliver_mail_on_route(const structures::unique_int &route, ethread &carrier); - //!< for internal use only--delivers the letters to known routes. - /*!< this function should only be used internally to prompt the delivery - of packages that are waiting for objects we have a route to. it returns - true when all items that were waiting have been sent. */ - -private: - basis::mutex c_mutt; //!< protects our lists and dead letter office from corruption. - mailbox *_post; //!< the items awaiting handling. - route_map *_routes; //!< the pathways that have been defined. - timely::time_stamp *_next_cleaning; //!< when the next mailbox flush will occur. - thread_cabinet *_threads; //!< our list of threads for postal routes. - - void get_route_list(structures::int_set &route_set); - //!< retrieves the list of routes that we have registered. - - void clean_package_list(post_office &post, letter_morph &to_clean); - //!< recycles all of the letters held in "to_clean". -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/process_control.cpp b/core/library/processes/process_control.cpp deleted file mode 100644 index ce0dbf03..00000000 --- a/core/library/processes/process_control.cpp +++ /dev/null @@ -1,606 +0,0 @@ - -//NOTE: -// -// this thing is showing bad behavior on win32 when unicode is enabled. -// therefore unicode is currently disabled for win32, which is a shame. -// but something needs to be fixed in our unicode conversion stuff; the unicode versions -// of the file names were not getting correctly back-converted into the ascii counterpart, -// and *that* is broken. -// ** this may be a widespread issue in the win32 code related to unicode right now! -// ** needs further investigation when a reason to better support ms-windows emerges. -// -// => but don't panic... right now things work as long as you do a utf-8 / ascii build, -// which is how everything's configured. - - -/* -* Name : process_control -* Author : Chris Koeritz -** -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "process_entry.h" -#include "process_control.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; - -namespace processes { - -#ifdef __WIN32__ - #include - const astring NTVDM_NAME = "ntvdm.exe"; - // the umbrella process that hangs onto 16 bit tasks for NT. - #ifdef _MSCVER - #include - #endif -#endif -#ifdef __UNIX__ - #include - #include -#endif - -//#define DEBUG_PROCESS_CONTROL - // uncomment for noisier debugging. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -////////////// - -class process_implementation_hider -{ -public: -#ifdef __WIN32__ - // psapi members: - application_instance psapi_dll; - application_instance vdm_dll; - BOOL (WINAPI *enumerate_processes)(basis::un_int *, basis::un_int cb, basis::un_int *); - BOOL (WINAPI *enumerate_modules)(HANDLE, HMODULE *, basis::un_int, basis::un_int *); - basis::un_int (WINAPI *get_module_name)(HANDLE, HMODULE, LPTSTR, basis::un_int); -#ifdef _MSCVER - INT (WINAPI *tasker_16bit)(basis::un_int, TASKENUMPROCEX fp, LPARAM); -#endif - - // toolhelp members: - application_instance kernel32_dll; - HANDLE (WINAPI *create_snapshot)(basis::un_int,basis::un_int); - BOOL (WINAPI *first_process)(HANDLE,LPPROCESSENTRY32); - BOOL (WINAPI *next_process)(HANDLE,LPPROCESSENTRY32); - - // get an atomic view of the process list, which rapidly becomes out of date. -/// HANDLE hSnapShot; - - process_implementation_hider() - : psapi_dll(NIL), vdm_dll(NIL), enumerate_processes(NIL), - enumerate_modules(NIL), get_module_name(NIL), -#ifdef _MSCVER - tasker_16bit(NIL), -#endif - kernel32_dll(NIL), create_snapshot(NIL), first_process(NIL), - next_process(NIL) {} - - ~process_implementation_hider() { - if (psapi_dll) FreeLibrary(psapi_dll); - if (vdm_dll) FreeLibrary(vdm_dll); - if (kernel32_dll) FreeLibrary(kernel32_dll); - psapi_dll = NIL; - vdm_dll = NIL; - kernel32_dll = NIL; - } -#endif -}; - -////////////// - -class process_info_clump -{ -public: - basis::un_int _process_id; - process_entry_array &_to_fill; // where to add entries. - - process_info_clump(basis::un_int id, process_entry_array &to_fill) - : _process_id(id), _to_fill(to_fill) {} -}; - -////////////// - -// top-level functions... - -process_control::process_control() -: _ptrs(new process_implementation_hider), -#ifdef __WIN32__ - _use_psapi(true), -#endif -#ifdef __UNIX__ - _rando(new chaos), -#endif - _healthy(false) -{ - // Check to see if were running under Windows95 or Windows NT. - version osver = application_configuration::get_OS_version(); - -#ifdef __WIN32__ - if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) { - // we're on Windows 95, so use the toolhelp API for the processes. - _use_psapi = false; - } else if (osver.v_major() >= 5) { - // w2k and onward can do the toolhelp instead of psapi. - _use_psapi = false; - } - if (_use_psapi) - _healthy = initialize_psapi_support(); - else - _healthy = initialize_toolhelp_support(); -#endif -#ifdef __UNIX__ - _healthy = true; -#endif -} - -process_control::~process_control() -{ - WHACK(_ptrs); -#ifdef __UNIX__ - WHACK(_rando); -#endif -} - -void process_control::sort_by_name(process_entry_array &v) -{ - process_entry temp; - for (int gap = v.length() / 2; gap > 0; gap /= 2) - for (int i = gap; i < v.length(); i++) - for (int j = i - gap; j >= 0 - && (filename(v[j].path()).basename().raw() - > filename(v[j + gap].path()).basename().raw()); - j = j - gap) - { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; } -} - -void process_control::sort_by_pid(process_entry_array &v) -{ - process_entry temp; - for (int gap = v.length() / 2; gap > 0; gap /= 2) - for (int i = gap; i < v.length(); i++) - for (int j = i - gap; j >= 0 && (v[j]._process_id - > v[j + gap]._process_id); j = j - gap) - { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; } -} - -bool process_control::query_processes(process_entry_array &to_fill) -{ - if (!_healthy) return false; -#ifdef __WIN32__ - if (!_use_psapi) { - // we're on Windows 95 or something, so use the toolhelp API for the - // processes. - return get_processes_with_toolhelp(to_fill); - } else { - // we're on Windows NT and so on; use the process API (PSAPI) to get info. - return get_processes_with_psapi(to_fill); - } -#endif -#ifdef __UNIX__ - return get_processes_with_ps(to_fill); -#endif -} - -#ifdef __WIN32__ -bool process_control::initialize_psapi_support() -{ - // create an instance of the PSAPI dll for querying 32-bit processes and - // an instance of the VDM dll support just in case there are also some - // 16-bit processes. - _ptrs->psapi_dll = LoadLibraryA("psapi.dll"); - if (!_ptrs->psapi_dll) return false; - _ptrs->vdm_dll = LoadLibraryA("vdmdbg.dll"); - if (!_ptrs->vdm_dll) return false; - - // locate the functions that we want to call. - _ptrs->enumerate_processes = (BOOL(WINAPI *)(basis::un_int *,basis::un_int,basis::un_int*)) - GetProcAddress(_ptrs->psapi_dll, "EnumProcesses"); - _ptrs->enumerate_modules - = (BOOL(WINAPI *)(HANDLE, HMODULE *, basis::un_int, basis::un_int *)) - GetProcAddress(_ptrs->psapi_dll, "EnumProcessModules"); - _ptrs->get_module_name - = (basis::un_int (WINAPI *)(HANDLE, HMODULE, LPTSTR, basis::un_int)) - GetProcAddress(_ptrs->psapi_dll, "GetModuleFileNameExA"); -#ifdef _MSCVER - _ptrs->tasker_16bit = (INT(WINAPI *)(basis::un_int, TASKENUMPROCEX, LPARAM)) - GetProcAddress(_ptrs->vdm_dll, "VDMEnumTaskWOWEx"); -#endif - if (!_ptrs->enumerate_processes || !_ptrs->enumerate_modules - || !_ptrs->get_module_name -#ifdef _MSCVER - || !_ptrs->tasker_16bit -#endif - ) return false; - - return true; -} - -bool process_control::initialize_toolhelp_support() -{ - // get hooked up with the kernel dll so we can use toolhelp functions. - _ptrs->kernel32_dll = LoadLibraryA("Kernel32.DLL"); - if (!_ptrs->kernel32_dll) return false; - - // create pointers to the functions we want to invoke. - _ptrs->create_snapshot = (HANDLE(WINAPI *)(basis::un_int,basis::un_int)) - GetProcAddress(_ptrs->kernel32_dll, "CreateToolhelp32Snapshot"); - _ptrs->first_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32)) - GetProcAddress(_ptrs->kernel32_dll, "Process32First"); - _ptrs->next_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32)) - GetProcAddress(_ptrs->kernel32_dll, "Process32Next"); - if (!_ptrs->next_process || !_ptrs->first_process - || !_ptrs->create_snapshot) return false; - return true; -} - -#endif - -bool process_control::zap_process(basis::un_int to_zap) -{ -#ifdef DEBUG_PROCESS_CONTROL - FUNCDEF("zap_process"); -#endif - if (!_healthy) return false; -#ifdef __UNIX__ - int ret = kill(to_zap, 9); - // send the serious take-down signal to the process. - return !ret; -#endif -#ifdef __WIN32__ - HANDLE h = OpenProcess(PROCESS_TERMINATE, false, to_zap); - if (!h) { -#ifdef DEBUG_PROCESS_CONTROL - int err = critical_events::system_error(); - LOG(a_sprintf("error zapping process %d=", to_zap) - + critical_events::system_error_text(err)); -#endif - return false; - } - int exit_code = 0; - BOOL ret = TerminateProcess(h, exit_code); - CloseHandle(h); - return !!ret; -#endif -} - -process_entry process_control::query_process(basis::un_int to_query) -{ -// FUNCDEF("query_process"); - process_entry to_return; - - process_entry_array to_fill; - bool got_em = query_processes(to_fill); - if (!got_em) return to_return; - - for (int i = 0; i < to_fill.length(); i++) { - if (to_fill[i]._process_id == to_query) - return to_fill[i]; - } - -//hmmm: implement more specifically. -#ifdef __UNIX__ -//put in the single process grabber deal. -#endif -#ifdef __WIN32__ -//grab the entry from the list. -#endif - - return to_return; -} - -bool process_control::find_process_in_list(const process_entry_array &processes, - const astring &app_name_in, int_set &pids) -{ -#ifdef DEBUG_PROCESS_CONTROL - FUNCDEF("find_process_in_list"); -#endif - pids.clear(); - astring app_name = app_name_in.lower(); - - version os_ver = application_configuration::get_OS_version(); - - bool compare_prefix = (os_ver.v_major() == 5) && (os_ver.v_minor() == 0); - // we only compare the first 15 letters due to a recently noticed bizarre - // bug where w2k only shows (and reports) the first 15 letters of file - // names through toolhelp. - - bool found = false; // was it seen in process list? - for (int i = 0; i < processes.length(); i++) { - filename path = processes[i].path(); - astring base = path.basename().raw().lower(); - // a kludge for w2k is needed--otherwise we will miss seeing names that - // really are running due to the toolhelp api being busted and only - // reporting the first 15 characters of the name. - if ( (compare_prefix && (base.compare(app_name, 0, 0, 15, false))) - || (base == app_name) ) { - found = true; - pids.add(processes[i]._process_id); - } - } -#ifdef DEBUG_PROCESS_CONTROL - if (!found) - LOG(astring("failed to find the program called ") + app_name); -#endif - return found; -} - -////////////// - -#ifdef __WIN32__ -// this section is the PSAPI version of the query. - -// called back on each 16 bit task. -BOOL WINAPI process_16bit(basis::un_int dwThreadId, WORD module_handle16, WORD hTask16, - PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined) -{ - process_info_clump *to_stuff = (process_info_clump *)lpUserDefined; - process_entry to_add; - to_add._process_id = to_stuff->_process_id; - to_add._module16 = hTask16; - to_add.path(pszFileName); -//threads, etc? - to_stuff->_to_fill += to_add; - return true; -} - -bool process_control::get_processes_with_psapi(process_entry_array &to_fill) -{ - // prepare the result object. - to_fill.reset(); - - // loop over the process enumeration function until we are sure that we - // have allocated a large enough space for all existing processes. - bool got_all = false; - basis::un_int *pid_list = NIL; - basis::un_int max_size = 428 * sizeof(basis::un_int); - basis::un_int actual_size = 0; - while (!got_all) { - pid_list = (basis::un_int *)HeapAlloc(GetProcessHeap(), 0, max_size); - if (!pid_list) return false; - if (!_ptrs->enumerate_processes(pid_list, max_size, &actual_size)) { - HeapFree(GetProcessHeap(), 0, pid_list); - return false; - } - if (actual_size == max_size) { - // there were too many to store, so whack the partial list. - HeapFree(GetProcessHeap(), 0, pid_list); - max_size *= 2; // try with twice as much space. - } else got_all = true; - } - - // calculate the number of process ids that got stored. - basis::un_int ids = actual_size / sizeof(basis::un_int); - - // examine each process id that we found. - for (basis::un_int i = 0; i < ids; i++) { - // get process information if security permits. -//turn chunk below into "scan process" or something. - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - false, pid_list[i]); - flexichar process_name[MAX_ABS_PATH + 1] = { '\0' }; - if (hProcess) { - // go over the modules for the process. the first will be the main - // application module and the others will be threads. ??? - basis::un_int max_size = 1 * sizeof(HMODULE); -//hmmm: could do a rescan loop here if too many. - basis::un_int actual_size = 0; - HMODULE *module_handles = new HMODULE[max_size + 1]; - if (!module_handles) { - CloseHandle(hProcess); - HeapFree(GetProcessHeap(), 0, pid_list); - return false; - } - if (_ptrs->enumerate_modules(hProcess, module_handles, max_size, - &actual_size)) { - // we want the name of the first module. - if (!_ptrs->get_module_name(hProcess, *module_handles, process_name, - sizeof(process_name))) - process_name[0] = 0; - } - WHACK(module_handles); - CloseHandle(hProcess); - } - - // we add whatever information we were able to find about this process. - process_entry new_entry; - new_entry._process_id = pid_list[i]; - astring converted_name = from_unicode_temp(process_name); - new_entry.path(converted_name); - -//how to get? performance data helper? -/// new_entry._threads = threads; - to_fill += new_entry; - - // if we're looking at ntvdm, then there might be 16 bit processes - // attached to it. - if (new_entry.path().length() >= NTVDM_NAME.length()) { - astring temp = new_entry.path().substring - (new_entry.path().end() - NTVDM_NAME.length() + 1, - new_entry.path().end()); - temp.to_lower(); -#ifdef _MSCVER -//hmmm: pull this back in for mingw when it seems to be supported, if ever. - if (temp == NTVDM_NAME) { - // set up a callback stampede on the 16 bit processes. - process_info_clump info(pid_list[i], to_fill); - _ptrs->tasker_16bit(pid_list[i], (TASKENUMPROCEX)process_16bit, - (LPARAM)&info); - } -#endif - } - } - - if (pid_list) HeapFree(GetProcessHeap(), 0, pid_list); - return true; -} - -////////////// - -// this is the toolhelp version of the query. - -bool process_control::get_processes_with_toolhelp(process_entry_array &to_fill) -{ - // prepare the result object. - to_fill.reset(); - - // get an atomic view of the process list, which rapidly becomes out of date. - HANDLE hSnapShot; - hSnapShot = _ptrs->create_snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapShot == INVALID_HANDLE_VALUE) return false; - - // start iterating through the snapshot by getting the first process. - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); - BOOL keep_going = _ptrs->first_process(hSnapShot, &entry); - - // while we see valid processes, iterate through them. - while (keep_going) { - // add an entry for the current process. - process_entry new_entry; - new_entry._process_id = entry.th32ProcessID; - new_entry._references = entry.cntUsage; - new_entry._threads = entry.cntThreads; - new_entry._parent_process_id = entry.th32ParentProcessID; - astring exe_file = from_unicode_temp(entry.szExeFile); - new_entry.path(exe_file); - to_fill += new_entry; - entry.dwSize = sizeof(PROCESSENTRY32); // reset struct size. - keep_going = _ptrs->next_process(hSnapShot, &entry); - } - - CloseHandle(hSnapShot); - return true; -} -#endif // __WIN32__ - -#ifdef __UNIX__ - -#define CLOSE_TMP_FILE { \ -/* continuable_error("process_control", "get_processes_with_ps", error); */ \ - if (output) { \ - fclose(output); \ - unlink(tmpfile.s()); \ - } \ -} - -bool process_control::get_processes_with_ps(process_entry_array &to_fill) -{ - FUNCDEF("get_processes_with_ps"); - to_fill.reset(); - // we ask the operating system to give us a list of processes. - a_sprintf tmpfile("/tmp/proc_list_%d_%d.txt", application_configuration::process_id(), - _rando->inclusive(1, 400000)); - a_sprintf cmd("ps wax --format \"%%p %%a\" >%s", tmpfile.s()); -//hmmm: add more info as we expand the process entry. - FILE *output = NIL; // initialize now to establish variable for our macro. - int sysret = system(cmd.s()); - if (negative(sysret)) { -LOG("got negative return from system()!"); - CLOSE_TMP_FILE; - return false; - } - output = fopen(tmpfile.s(), "r"); - if (!output) { -LOG("failed to open process list file!"); - CLOSE_TMP_FILE; - return false; - } - const int max_buff = 10000; - char buff[max_buff]; - size_t size_read = 1; - astring accumulator; - while (size_read > 0) { - // read bytes from the file. - size_read = fread(buff, 1, max_buff, output); - // if there was anything, store it in the string. - if (size_read > 0) - accumulator += astring(astring::UNTERMINATED, buff, size_read); - } - CLOSE_TMP_FILE; - // parse the string up now. - bool first_line = true; - while (accumulator.length()) { - // eat any spaces in front. - if (first_line) { - // we toss the first line since it's a header with columns. - int cr_indy = accumulator.find('\n'); - accumulator.zap(0, cr_indy); - if (accumulator[accumulator.end()] == '\r') - accumulator.zap(accumulator.end(), accumulator.end()); - first_line = false; - continue; - } - while (accumulator.length() && (accumulator[0] == ' ')) - accumulator.zap(0, 0); - // look for the first part of the line; the process id. - int num_indy = accumulator.find(' '); - if (negative(num_indy)) break; - basis::un_int p_id = accumulator.substring(0, num_indy).convert(0); - accumulator.zap(0, num_indy); - int cr_indy = accumulator.find('\n'); - if (negative(cr_indy)) - cr_indy = accumulator.end() + 1; - astring proc_name = accumulator.substring(0, cr_indy - 1); - if (proc_name[proc_name.end()] == '\r') - proc_name.zap(proc_name.end(), proc_name.end()); - accumulator.zap(0, cr_indy); - int space_indy = proc_name.find(' '); -//hmmm: this is incorrect regarding names that do have spaces in them. - if (negative(space_indy)) - space_indy = proc_name.end() + 1; - process_entry to_add; - to_add._process_id = p_id; - astring path = proc_name.substring(0, space_indy - 1); - // patch the pathname if we see any bracketed items. - int brackets_in = 0; - for (int i = 0; i < path.length(); i++) { - if (path[i] == '[') brackets_in++; - else if (path[i] == ']') brackets_in--; - if (brackets_in) { - // if we see a slash inside brackets, then we patch it so it doesn't - // confuse the filename object's directory handling. - if ( (path[i] == '/') || (path[i] == '\\') ) - path[i] = '#'; - } - } - to_add.path(path); - to_fill += to_add; - } - return true; -} -#endif // __UNIX__ - -} //namespace. - diff --git a/core/library/processes/process_control.h b/core/library/processes/process_control.h deleted file mode 100644 index c6d25861..00000000 --- a/core/library/processes/process_control.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef PROCESS_CONTROL_CLASS -#define PROCESS_CONTROL_CLASS - -/*****************************************************************************\ -* * -* Name : process_control * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "process_entry.h" - -#include -#include -#include - -namespace processes { - -// forward. -class process_entry_array; -class process_implementation_hider; - -//! Provides a bridge to the operating system for information on processes. -/*! - This object can query the operating system for the current set of processes - or zap a particular process of interest. -*/ - -class process_control : public virtual basis::nameable -{ -public: - process_control(); - virtual ~process_control(); - - DEFINE_CLASS_NAME("process_control"); - - bool healthy() const { return _healthy; } - //!< returns true if this object should be functional. - /*!< if it failed to construct properly, this returns false. usually a - failure indicates that a required dynamic library is missing, such as - "psapi.dll" on win32. */ - - process_entry query_process(basis::un_int to_query); - //!< returns the information for just one process. - - bool query_processes(process_entry_array &to_fill); - //!< finds the processes that are running and drops them into "to_fill". - - bool zap_process(basis::un_int to_zap); - //!< preemptively zaps the process "to_zap". - /*!< this does not invoke any friendly graceful shut down process, but - merely terminates it if possible. */ - - static bool find_process_in_list(const process_entry_array &processes, - const basis::astring &app_name, structures::int_set &pids); - //!< uses a pre-existing list of "processes" to search for the "app_name". - /*!< if the process is found, true is returned and the "pids" are set to - all entries matching the process name. note that this is an approximate - match for some OSes that have a brain damaged process lister (such as - ms-windows); they have programs listed under incomplete names in some - cases. */ - - void sort_by_name(process_entry_array &to_sort); - // sorts the list by process name. - void sort_by_pid(process_entry_array &to_sort); - // sorts the list by process id. - -private: - process_implementation_hider *_ptrs; //!< our OS baggage. -#ifdef __UNIX__ - mathematics::chaos *_rando; //!< used for process list. -#endif -#ifdef __WIN32__ - bool _use_psapi; //!< true if we should be using the PSAPI on NT and family. -#endif - bool _healthy; //!< true if construction succeeded. - -#ifdef __UNIX__ - bool get_processes_with_ps(process_entry_array &to_fill); - //!< asks the ps program what processes exist. -#endif -#ifdef __WIN32__ - // fill in our function pointers to access the kernel functions appropriate - // for either NT (psapi) or 9x (toolhelp). - bool initialize_psapi_support(); - bool initialize_toolhelp_support(); - - bool get_processes_with_psapi(process_entry_array &to_fill); - //!< uses the PSAPI support for windows NT 4 (or earlier?). - bool get_processes_with_toolhelp(process_entry_array &to_fill); - //!< uses the toolhelp support for windows 9x, ME, 2000. -#endif -}; - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/processes/process_entry.cpp b/core/library/processes/process_entry.cpp deleted file mode 100644 index 40cf3589..00000000 --- a/core/library/processes/process_entry.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/*****************************************************************************\ -* * -* Name : process_entry * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "process_entry.h" - -#include -#include -#include - -using namespace basis; -using namespace filesystem; - -namespace processes { - -process_entry::process_entry() -: _process_id(0), - _references(0), - _threads(0), - _parent_process_id(0), - _module16(0), - _process_path(new astring) -{} - -process_entry::process_entry(const process_entry &to_copy) -: _process_id(0), - _references(0), - _threads(0), - _parent_process_id(0), - _module16(0), - _process_path(new astring) -{ - operator =(to_copy); -} - -process_entry::~process_entry() -{ - WHACK(_process_path); -} - -void process_entry::text_form(basis::base_string &fill) const -{ - fill = text_form(); -} - -process_entry &process_entry::operator =(const process_entry &to_copy) -{ - if (&to_copy == this) return *this; - _process_id = to_copy._process_id; - _references = to_copy._references; - _threads = to_copy._threads; - _parent_process_id = to_copy._parent_process_id; - *_process_path = *to_copy._process_path; - _module16 = to_copy._module16; - return *this; -} - -const astring &process_entry::path() const { return *_process_path; } - -void process_entry::path(const astring &new_path) -{ *_process_path = new_path; } - -astring process_entry::text_form() const -{ -#ifdef __UNIX__ - filename pat(path()); -#else - filename pat(path().lower()); -#endif - return a_sprintf("%s: pid=%u refs=%u thrd=%u par=%u mod16=%u path=%s", - pat.basename().raw().s(), _process_id, _references, _threads, - _parent_process_id, _module16, pat.dirname().raw().s()); -} - -} //namespace. - diff --git a/core/library/processes/process_entry.h b/core/library/processes/process_entry.h deleted file mode 100644 index 7c30b31b..00000000 --- a/core/library/processes/process_entry.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef PROCESS_ENTRY_CLASS -#define PROCESS_ENTRY_CLASS - -/*****************************************************************************\ -* * -* Name : process_entry * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -namespace processes { - -//! Encapsulates information about OS processes. - -class process_entry : public virtual basis::text_formable -{ -public: - basis::un_int _process_id; //!< the OS identifier of this process. - basis::un_int _references; //!< the number of references to (users of) this process. - basis::un_int _threads; //!< the number of threads in use by this process. - basis::un_int _parent_process_id; //!< the process id of the owning process. - basis::un_short _module16; //!< non-zero if this process is a 16-bit application. - - process_entry(); - process_entry(const process_entry &to_copy); - ~process_entry(); - - DEFINE_CLASS_NAME("process_entry"); - - process_entry &operator =(const process_entry &to_copy); - - const basis::astring &path() const; - void path(const basis::astring &new_path); - - basis::astring text_form() const; - //!< returns a descriptive string for the information here. - - void text_form(basis::base_string &fill) const; //!< base class requirement. - -private: - basis::astring *_process_path; -}; - -////////////// - -//! a handy class that implements an array of process entries. - -class process_entry_array : public basis::array {}; - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/processes/rendezvous.cpp b/core/library/processes/rendezvous.cpp deleted file mode 100644 index 3f3385e8..00000000 --- a/core/library/processes/rendezvous.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/*****************************************************************************\ -* * -* Name : rendezvous * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//note: after repeated investigation, it seems that if we unlink the rendezvous -// file on destruction, then this hoses up any locks attempted ever after. -// instead of waiting for a lock, new attempts think they can go ahead, -// even though someone else might also have been given the lock. it seems -// we cannot remove the files without destroying the semantics. - -#include "rendezvous.h" - -#include -#include -#include -#include -#include - -#ifdef __UNIX__ - #include - #include - #include -#endif - -using namespace basis; -using namespace filesystem; - -namespace processes { - -//#define DEBUG_RENDEZVOUS - // uncomment for a noisier file. - -#undef LOG -#define LOG(tp) printf("%s%s\n", time_stamp::notarize(true).s(), astring(tp).s()) - // we can only use simple logging here since the rendezvous is relied on - // at very low levels and use of a log_base object would cause infinite - // loops. - -// used for the name of a mutex or part of the unix lock filename. -astring general_lock_name(const astring &root_part) -{ return root_part + "_app_lock"; } - -#ifdef __UNIX__ -// the name of the locking file used in unix. -astring unix_rendez_file(const astring &lock_name) -{ - astring clean_name = lock_name; - // remove troublesome characters from the name. - filename::detooth_filename(clean_name); - // make sure our target directory exists. - - // this choice is only user specific. -// astring tmp_dir = portable::env_string("TMP") + "/rendezvous"; - - // this choice uses a system-wide location. - astring tmp_dir = "/tmp/rendezvous"; - - mkdir(tmp_dir.observe(), 0777); - return tmp_dir + "/ren_" + clean_name; -} -#endif - -rendezvous::rendezvous(const astring &root_name) -: _handle(NIL), - _locked(false), - _root_name(new astring(root_name)) -{ -#ifdef DEBUG_RENDEZVOUS - FUNCDEF("constructor"); -#endif - astring lock_name = general_lock_name(root_name); -#ifdef __UNIX__ - astring real_name = unix_rendez_file(lock_name); - FILE *locking_file = fopen(real_name.s(), "wb"); - if (!locking_file) { -#ifdef DEBUG_RENDEZVOUS - LOG(astring("failure to create locking file ") + real_name - + ": " + critical_events::system_error_text(critical_events::system_error()) ); -#endif - return; - } - // success now. - _handle = locking_file; -#endif -#ifdef __WIN32__ - _handle = CreateMutex(NIL, false, to_unicode_temp(lock_name)); - if (!_handle) return; -#endif -} - -rendezvous::~rendezvous() -{ -#ifdef DEBUG_RENDEZVOUS - FUNCDEF("destructor"); - LOG("okay, into destructor for rendezvous."); -#endif -#ifdef __UNIX__ - if (_handle) { - if (_locked) { - int ret = lockf(fileno((FILE *)_handle), F_ULOCK, sizeof(int)); - if (ret) { -#ifdef DEBUG_RENDEZVOUS - LOG("failure to get lock on file."); -#endif - } - _locked = false; // clear our locked status since we no longer have one. - -//note: after repeated investigation, it seems that if we unlink the rendezvous -// file on destruction, then this hoses up any locks attempted ever after. -// instead of waiting for a lock, new attempts think they can go ahead, -// even though someone else might also have been given the lock. it seems -// we cannot remove the files without destroying the semantics. - } - - fclose((FILE *)_handle); - _handle = NIL; - } -#endif -#ifdef __WIN32__ - if (_handle) CloseHandle((HANDLE)_handle); -#endif - WHACK(_root_name); -} - -void rendezvous::establish_lock() { lock(); } - -void rendezvous::repeal_lock() { unlock(); } - -bool rendezvous::healthy() const -{ - return !!_handle; -} - -bool rendezvous::lock(locking_methods how) -{ -#ifdef DEBUG_RENDEZVOUS - FUNCDEF("lock"); -#endif - if (how == NO_LOCKING) return false; - if (!healthy()) return false; -#ifdef __UNIX__ - int command = F_TLOCK; - if (how == ENDLESS_WAIT) command = F_LOCK; - int ret = lockf(fileno((FILE *)_handle), command, sizeof(int)); - if (ret) { -#ifdef DEBUG_RENDEZVOUS - LOG("failure to get lock on file."); -#endif - return false; - } -#ifdef DEBUG_RENDEZVOUS - LOG("okay, got lock on shared mem."); -#endif - _locked = true; - return true; -#endif -#ifdef __WIN32__ - int timing = 0; // immediate return. - if (how == ENDLESS_WAIT) timing = INFINITE; - int ret = WaitForSingleObject((HANDLE)_handle, timing); - if ( (ret == WAIT_ABANDONED) || (ret == WAIT_TIMEOUT) ) return false; - else if (ret != WAIT_OBJECT_0) { -#ifdef DEBUG_RENDEZVOUS - LOG("got an unanticipated error from waiting for the mutex."); -#endif - return false; - } - _locked = true; - return true; -#endif - return false; -} - -void rendezvous::unlock() -{ -#ifdef DEBUG_RENDEZVOUS - FUNCDEF("unlock"); -#endif - if (!healthy()) return; - if (_locked) { -#ifdef __UNIX__ - int ret = lockf(fileno((FILE *)_handle), F_ULOCK, sizeof(int)); - if (ret) { -#ifdef DEBUG_RENDEZVOUS - LOG("failure to get lock on file."); -#endif - } -#endif -#ifdef __WIN32__ - ReleaseMutex((HANDLE)_handle); -#endif - _locked = false; - } else { -#ifdef DEBUG_RENDEZVOUS - LOG("okay, rendezvous wasn't locked."); -#endif - } -} - -} //namespace. - diff --git a/core/library/processes/rendezvous.h b/core/library/processes/rendezvous.h deleted file mode 100644 index b04f3bd4..00000000 --- a/core/library/processes/rendezvous.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef RENDEZVOUS_CLASS -#define RENDEZVOUS_CLASS - -/*****************************************************************************\ -* * -* Name : rendezvous * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace processes { - -//! An inter-process synchronization primitive. -/*! - A lock can be created that only one process owns at a time; those that do - not acquire the lock can either return immediately or wait until the current - lock owner releases the rendezvous. This is unlike the mutex object in - basis, since mutexes only synchronize within the same application. The - rendezvous can instead allow synchronization of resources between - applications, but this comes at a higher cost per usage. -*/ - -class rendezvous : public virtual basis::base_synchronizer -{ -public: - rendezvous(const basis::astring &root_name); - //!< the healthy() method should be checked to ensure creation succeeded. - - virtual ~rendezvous(); - //!< any lock held is released and the lower level structures freed. - - DEFINE_CLASS_NAME("rendezvous"); - - bool healthy() const; - //!< returns true if the rendezvous object is operable. - /*!< there are cases where creation of the rendezvous might fail; they - can be trapped here. */ - - //! different ways that the lock() attempt can be made. - enum locking_methods { NO_LOCKING, ENDLESS_WAIT, IMMEDIATE_RETURN }; - - bool lock(locking_methods how = ENDLESS_WAIT); - //!< grab the lock, if possible. - /*!< if this is not the first time locking the same rendezvous, that's - fine as long as the number of unlocks matches the number of locks. */ - - void unlock(); - //!< releases a previously acquired lock. - - // these two methods implement the synchronizer_base interface. - virtual void establish_lock(); - virtual void repeal_lock(); - - const basis::astring &root_name() const; - //!< returns the root name passed in the constructor. - -private: - void *_handle; //!< the real OS version of the inter-app lock. - bool _locked; //!< true if the lock was successful and still locked. - basis::astring *_root_name; //!< the identifier for this rendezvous. -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/safe_callback.cpp b/core/library/processes/safe_callback.cpp deleted file mode 100644 index ca62be94..00000000 --- a/core/library/processes/safe_callback.cpp +++ /dev/null @@ -1,174 +0,0 @@ - - - -/*****************************************************************************\ -* * -* Name : safe_callback * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "safe_callback.h" - -#include -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace loggers; -using namespace structures; - -namespace processes { - -////////////// - -callback_data_block::~callback_data_block() {} - -////////////// - -class live_object_info -{ -public: - int _references; // the number of times it's been added to the list. - - live_object_info() : _references(1) {} -}; - -////////////// - -static bool _live_objects_are_gone = false; - // flags when the global_live_objects singleton winks out of existence. this - // should prevent ordering conflicts during the static destructions. - -class global_live_objects : public virtual root_object -{ -public: - global_live_objects() : _objects(rotating_byte_hasher(), 12) {} - // note that we have about a 2 billion callback object limit currently. - - ~global_live_objects() { _live_objects_are_gone = true; } - - DEFINE_CLASS_NAME("global_live_objects"); - - // returns true if the "object" is listed as valid. - bool listed(void *object) { - auto_synchronizer l(_lock); - live_object_info *loi = NIL; - return _objects.find(object, loi); - } - - // adds the "object" to the list, or if it's already there, ups the refcount. - void add(void *object) { - auto_synchronizer l(_lock); - live_object_info *loi = NIL; - if (!_objects.find(object, loi)) { - // this is a new item. - _objects.add(object, new live_object_info); - return; - } - // this item already exists. - loi->_references++; - } - - // reduces the refcount on the "object" and removes it if there are zero - // references. - void remove(void *object) { - auto_synchronizer l(_lock); - live_object_info *loi = NIL; - if (!_objects.find(object, loi)) { - // this item doesn't exist??? bad usage has occurred.. - return; - } - // patch its reference count. - loi->_references--; - if (!loi->_references) { - // ooh, it croaked. get rid of it now. - _objects.zap(object); - } - } - -private: - mutex _lock; // protects our list. - hash_table _objects; - // the valid objects are listed here. -}; - -////////////// - -safe_callback::safe_callback() -: _decoupled(false), - _callback_lock(new mutex) -{ begin_availability(); } - -safe_callback::~safe_callback() -{ - if (!_decoupled) - non_continuable_error(class_name(), "destructor", - "the derived safe_callback has not called end_availability() yet.\r\n" - "this violates caveat two of safe_callback (see header)."); - WHACK(_callback_lock); -} - -SAFE_STATIC(global_live_objects, safe_callback::_invocables, ) - -void safe_callback::begin_availability() -{ - // we don't lock the mutex here because there'd better not already be - // a call to the callback function that happens while the safe_callback - // object is still being constructed...! - - _decoupled = false; // set to be sure we know we ARE hooked in. - if (_live_objects_are_gone) return; // hosed. - _invocables().add(this); // list this object as valid. -} - -void safe_callback::end_availability() -{ - if (_decoupled) return; // never unhook any one safe_callback object twice. - if (_live_objects_are_gone) { - // nothing to unlist from. - _decoupled = true; - return; - } - _callback_lock->lock(); // protect access to this object. - _invocables().remove(this); // unlist this object. - _decoupled = true; // we are now out of the action. - _callback_lock->unlock(); // release lock again. - // we shoot the lock here so that people hear about it immediately. they - // will then be released from their pending but failed callback invocations. - WHACK(_callback_lock); -} - -bool safe_callback::invoke_callback(callback_data_block &new_data) -{ - auto_synchronizer l(*_callback_lock); - // we now have the lock. - if (!_invocables().listed(this)) return false; - // this object is no longer valid, so we must not touch it. - if (_decoupled) return false; - // object is partially decoupled already somehow. perhaps this instance - // has already been connected but another hook-up exists at some place - // else in the derivation hierarchy. they'd better be careful to shut - // down all the safe_callbacks before proceeding to destroy... - // if they do that, they're fine, since the shutdown of any owned data - // members would be postponed until after all the callbacks had been - // removed. - real_callback(new_data); - return true; -} - -} //namespace. - - - diff --git a/core/library/processes/safe_callback.h b/core/library/processes/safe_callback.h deleted file mode 100644 index 9ee33d2a..00000000 --- a/core/library/processes/safe_callback.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef SAFE_CALLBACK_CLASS -#define SAFE_CALLBACK_CLASS - -/*****************************************************************************\ -* * -* Name : safe_callback * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace processes { - -// forward. -class callback_data_block; -class global_live_objects; - -//! A reasonably easy way to make callbacks safe from shutdown ordering issues. -/*! - If an object implementing an unsafe, standard callback is destroyed, then - all of the pending callbacks on other threads are jeopardized. Objects with - unsafe objects will not fare well in a multi-threaded program at all. This - class ensures that no callback can ever occur on a dead object, but this - promise is subject to the caveats below. - - Caveat One: - - if you have synchronization control over objects that you own, please - DO NOT have them locked while your callback base class is being destroyed - NOR when you call the end_availability() method. allowing them to be locked - at those times can result in a program deadlock. ensuring this is a simple - matter of having a properly formed destructor, as in caveat two. - - Caveat Two: - - an object that implements safe_callback MUST invoke the end_availability - method as the FIRST thing in its destructor. otherwise, if some portions - of the object are shutdown before the safe_callback is stopped, then the - active callback invocation can have the rug pulled out from under it and - suddenly be working with bad objects. here is an example of a safe - destructor implementation: @code - - my_safe_callback::~my_safe_callback() { - // safely revoke this object's listing before other destructions. - end_availability(); - // destroy other objects now... - WHACK(first_thing); //...etc.... - } @endcode - - note also: if your object owns or is derived from more than one - safe_callback, then ALL of those must have their end_availability methods - invoked before ANY of the other objects owned can be destroyed. we - recommend against deriving from more than one safe_callback just for - simplicity's sake. -*/ - -class safe_callback -{ -public: - safe_callback(); - //!< construction signifies that the callback is now in operation. - - void end_availability(); - //!< prepares to shut down this object. - /*!< This removes the object from the list of available callbacks. this - MUST be called in a derived destructor BEFORE any other objects owned by - the derived class are destroyed. */ - - virtual ~safe_callback(); - //!< actually destroys the object. - /*!< don't allow this to be called without having invoked - end_availability() at the top of your derived class's destructor. - otherwise, you have broken your code by failing caveat two, above. */ - - DEFINE_CLASS_NAME("safe_callback"); - - bool decoupled() const { return _decoupled; } - //!< if true, then end_availability() was already invoked on the object. - /*!< if this is returning true, then one can trust that the object is - properly deregistered and safely decoupled from the callback. a return of - false means that the object still might be hooked into callbacks and it is - not yet safe to destroy the object. */ - - bool invoke_callback(callback_data_block &new_data); - //!< this function is invoked by a user of the safe_callback derived object. - /*!< this performs some safety checks before invoking the real_callback() - method. given the caveats are adhered to, we automatically ensure that the - object to be called actually exists in a healthy state. we also enforce - that the called object cannot be destroyed until after any active - callback invocation is finished, and that any pending callbacks are - rejected once the object is invalid. - true is returned if the safe callback was actually completed. a false - return indicates that the object had already shut down. perhaps that's - a sign that the caller should now remove the object if it hasn't already - been removed externally... certainly if the call is rejected more than - once, there's no reason to keep invoking the callback. - each safe_callback implements its own locking to ensure that the - object is still alive. thus, derived objects don't need to provide some - separate proof of their health. this also allows the derived object - to mostly ignore callback-related locking in its own synchronization - code (mostly--see caveats above). */ - -protected: - virtual void real_callback(callback_data_block &new_data) = 0; - //!< derived classes implement this to provide their callback functionality. - /*!< this call will be prevented from ever occurring on an invalid "this" - pointer (given the caveats above are adhered to). */ - -private: - bool _decoupled; //!< true if we have ended our availability. - basis::mutex *_callback_lock; //!< synchronizes access to this object. - void begin_availability(); - //!< allows the safe_callback derived object to be called. - /*!< if this was never invoked, then attempts to callback are rejected. */ - -public: - global_live_objects &_invocables(); - //!< provides access to the program-wide list of healthy callback objects. - /*!< this should not be used by anyone external to the safe_callback - implementation. */ -}; - -////////////// - -//! a simple place-holder that anonymizes the type passed to the callback. -/*! - the virtual destructor above ensures that RTTI can be used if necessary; - objects of different class types can be differentiated when passed to the - callback. -*/ -class callback_data_block -{ -public: - virtual ~callback_data_block(); -}; - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/processes/safe_roller.cpp b/core/library/processes/safe_roller.cpp deleted file mode 100644 index 85dd8acd..00000000 --- a/core/library/processes/safe_roller.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/*****************************************************************************\ -* * -* Name : safe_roller * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "safe_roller.h" - -#include -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace processes { - -SAFE_STATIC(mutex, __roller_synch, ) - -void safe_add(int &to_change, int addition) -{ - auto_synchronizer l(__roller_synch()); - to_change += addition; -} - -////////////// - -safe_roller::safe_roller(int start_of_range, int end_of_range) -: _rolling(new int_roller(start_of_range, end_of_range)), - _lock(new mutex) -{ -} - -safe_roller::~safe_roller() -{ - WHACK(_rolling); - WHACK(_lock); -} - -int safe_roller::next_id() -{ - _lock->lock(); - int to_return = _rolling->next_id(); - _lock->unlock(); - return to_return; -} - -int safe_roller::current() const -{ - _lock->lock(); - int to_return = _rolling->current(); - _lock->unlock(); - return to_return; -} - -void safe_roller::set_current(int new_current) -{ - _lock->lock(); - _rolling->set_current(new_current); - _lock->unlock(); -} - -} //namespace. - - diff --git a/core/library/processes/safe_roller.h b/core/library/processes/safe_roller.h deleted file mode 100644 index 6c9a1643..00000000 --- a/core/library/processes/safe_roller.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SAFE_ROLLER_CLASS -#define SAFE_ROLLER_CLASS - -/*****************************************************************************\ -* * -* Name : safe_roller * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace processes { - -//! Implements a thread-safe roller object. -/*! - Integers can be generated by this object without concern for corruption by - multiple threads. -*/ - -class safe_roller -{ -public: - safe_roller(int start_of_range = 0, int end_of_range = MAXINT32); - //!< Provides numbers between the start and end in a thread-safe way. - /*!< constructs a roller that runs from the value "start_of_range" and - will stay smaller than "end_of_range" (unless the initial parameters are - screwed up; this class doesn't validate the start and end of range). - when the roller hits the end of range, then its value is reset to the - "start_of_range" again. */ - - ~safe_roller(); - - int next_id(); - //!< returns a unique (per instance of this type) id. - - int current() const; - //!< returns the current id to be used; be careful! - /*!< this value will be the next one returned, so only look at the - current id and don't use it unwisely. this function is useful if you want - to assign an id provisionally but might not want to complete the - issuance of it. */ - - void set_current(int new_current); - //!< allows the current id to be manipulated. - /*!< this must be done with care lest existing ids be re-used. */ - -private: - structures::int_roller *_rolling; //!< the id generator. - basis::mutex *_lock; //!< thread synchronization. - - // forbidden... - safe_roller(const safe_roller &); - safe_roller &operator =(const safe_roller &); -}; - -////////////// - -void safe_add(int &to_change, int addition); - //!< thread-safe integer addition. - /*!< the number passed in "addition" is atomically added to the number - referenced in "to_change". this allows thread-safe manipulation of - a simple number. */ - -} //namespace. - -#endif - diff --git a/core/library/processes/state_machine.cpp b/core/library/processes/state_machine.cpp deleted file mode 100644 index 3704bf98..00000000 --- a/core/library/processes/state_machine.cpp +++ /dev/null @@ -1,505 +0,0 @@ -/*****************************************************************************\ -* * -* Name : state_machine * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "state_machine.h" - -#include -#include -#include -#include -#include -#include - -using namespace basis; -using namespace loggers; -using namespace timely; - -namespace processes { - -////////////// - -//#define DEBUG_STATE_MACHINE - // uncomment if you want the debugging version. - -//hmmm: implement logging... -#undef LOG -#ifndef DEBUG_STATE_MACHINE - #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) -#else - #define LOG(to_print) {} -#endif - -////////////// - -// checks whether the machine passed in is valid or not. -#define CHECK_VALID(m) \ - if (!m._current) return false; \ - if (!m._last) return false - -// checks whether the current and next states exist or not. -#define CHECK_STATES \ - if (!current) return false; \ - if (!next) return false; \ - int indy = state_index(current); \ - if (negative(indy)) return false; \ - if (negative(state_index(next))) return false - -// moves to a new state and resets the new state's timestamp. -#define MOVE_STATE(m, next, type, trigger) \ - m._last = m._current; \ - m._current = next; \ - m._type = type; \ - m._trig = trigger; \ - m._start->reset() - -// locates a state or returns. -#define FIND_STATE(state) \ - int indy = state_index(state); \ - if (negative(indy)) return - -////////////// - -struct override { int current; int next; int duration; - override(int _current = 0, int _next = 0, int _duration = 0) - : current(_current), next(_next), duration(_duration) {} -}; - -struct transition_info { - enum transition_type { SIMPLE, RANGE, TIMED }; - transition_type type; - int next_state; - int low_trigger, high_trigger; - int time_span; - - transition_info() {} // blank. - transition_info(int next) : type(SIMPLE), next_state(next) {} - transition_info(int next, int time) : type(TIMED), next_state(next), - time_span(time) {} - transition_info(int next, int low, int high) : type(RANGE), - next_state(next), low_trigger(low), high_trigger(high) {} -}; - -struct state_info { - int state_id; // id for this state. - array transitions; - - state_info() : state_id(0) {} // blank. - state_info(int state_id_in) : state_id(state_id_in) {} -}; - -////////////// - -class state_machine_override_array : public array {}; -class state_machine_state_array : public array {}; - -////////////// - -state_machine::state_machine() -: _current(0), - _last(0), - _trig(0), - _type(SIMPLE), - _start(new time_stamp()), - _name(new astring()), - _overrides(new state_machine_override_array) -{} - -state_machine::state_machine(const state_machine &to_copy) -: root_object(), - _current(0), - _last(0), - _trig(0), - _type(SIMPLE), - _start(new time_stamp()), - _name(new astring()), - _overrides(new state_machine_override_array) -{ *this = to_copy; } - -state_machine::~state_machine() -{ - WHACK(_start); - WHACK(_name); - WHACK(_overrides); -} - -int state_machine::update() { return 0; } - -void state_machine::set_name(const astring &name) { *_name = name; } - -astring state_machine::get_name() const { return *_name; } - -state_machine &state_machine::operator = (const state_machine &to_copy) -{ - if (&to_copy == this) return *this; - _current = to_copy._current; - _last = to_copy._last; - _trig = to_copy._trig; - _type = to_copy._type; - *_start = *to_copy._start; - *_name = *to_copy._name; - *_overrides = *to_copy._overrides; -//careful when overrides becomes hidden internal type. - return *this; -} - -int state_machine::duration_index(int current, int next) const -{ - for (int i = 0; i < _overrides->length(); i++) - if ( ((*_overrides)[i].current == current) - && ((*_overrides)[i].next == next) ) - return i; - return common::NOT_FOUND; -} - -void state_machine::set_state(int new_current, int new_last, int trigger, - transition_types type) -{ - _current = new_current; - _last = new_last; - _trig = trigger; - _type = type; - _start->reset(); -} - -void state_machine::override_timing(int current, int next, int duration) -{ - int indy = duration_index(current, next); - if (non_negative(indy)) { - // found an existing record for this current/next pair. - if (!duration) { - // zero duration means removal. - _overrides->zap(indy, indy); - return; - } - // reset the duration. - (*_overrides)[indy].duration = duration; - return; - } - // no existing record, so add one. - *_overrides += override(current, next, duration); -} - -int state_machine::duration_override(int current, int next) const -{ - int indy = duration_index(current, next); - if (negative(indy)) return 0; - return (*_overrides)[indy].duration; -} - -time_stamp state_machine::start() const { return *_start; } - -////////////// - -transition_map::transition_map() -: _valid(false), - _start_state(0), - _state_list(new state_machine_state_array) -{} - -transition_map::~transition_map() -{ - _valid = false; - WHACK(_state_list); -} - -// informational functions: - -int transition_map::states() const { return _state_list->length(); } - -int transition_map::state_index(int state_id) const -{ - for (int i = 0; i < states(); i++) - if ((*_state_list)[i].state_id == state_id) return i; - return common::NOT_FOUND; -} - -int transition_map::transition_index(int state_index, int next, int &start) -{ - bounds_return(state_index, 0, states() - 1, common::BAD_INPUT); - state_info &state = (*_state_list)[state_index]; - bounds_return(start, 0, state.transitions.length() - 1, common::BAD_INPUT); - // loop over the transitions by using our external index. - for (start = start; start < state.transitions.length(); start++) - if (state.transitions[start].next_state == next) { - start++; // position it after this index. - return start - 1; // return this index. - } - return common::NOT_FOUND; -} - -// configurational functions: - -void transition_map::reconfigure() { _valid = false; } - -outcome transition_map::validate(int &examine) -{ - // check for a bad starting state... - examine = _start_state; - FIND_STATE(_start_state) BAD_START; - - if (!check_reachability(examine)) return UNREACHABLE; - // a state is unreachable from the starting state. - if (!check_overlapping(examine)) return OVERLAPPING_RANGES; - // bad (overlapping) ranges were found in one state. - _valid = true; // set us to operational. - return OKAY; -} - -bool transition_map::add_state(int state_number) -{ - if (valid()) return false; // this is operational; no more config! - if (!state_number) return false; // zero is disallowed. - if (non_negative(state_index(state_number))) return false; // already exists. - *_state_list += state_info(state_number); - return true; -} - -bool transition_map::set_start(int starting_state) -{ - if (valid()) return false; // this is operational; no more config! - if (!starting_state) return false; - - FIND_STATE(starting_state) false; // doesn't exist. - - _start_state = starting_state; - return true; -} - -bool transition_map::add_simple_transition(int current, int next) -{ - if (valid()) return false; // this is operational; no more config! - CHECK_STATES; - (*_state_list)[indy].transitions += transition_info(next); - return true; -} - -bool transition_map::add_range_transition(int current, int next, int low, int high) -{ - if (valid()) return false; // this is operational; no more config! - CHECK_STATES; - (*_state_list)[indy].transitions += transition_info(next, low, high); - return true; -} - -bool transition_map::add_timed_transition(int current, int next, int length) -{ - if (valid()) return false; // this is operational; no more config! - CHECK_STATES; - state_info &found = (*_state_list)[indy]; - // locates any existing timed transition and re-uses its slot. - for (int i = 0; i < found.transitions.length(); i++) - if (found.transitions[i].type == transition_info::TIMED) { - found.transitions[i] = transition_info(next, length); - return true; - } - // no existing timed transition found, so add a new one. - (*_state_list)[indy].transitions += transition_info(next, length); - return true; -} - -// operational functions: - -bool transition_map::make_transition(state_machine &m, int next) -{ - if (!valid()) return false; // this is not operational yet! - CHECK_VALID(m); -#ifdef DEBUG_STATE_MACHINE - if (negative(state_index(m._current))) - LOG(astring("(%s) transition_map::make_transition: bad logic error; machine's " - "state is missing.", m._name->s())); - if (negative(state_index(next))) - LOG(astring("(%s) transition_map::make_transition: next state parameter is invalid.", - m._name->s())); -#endif - FIND_STATE(m._current) false; // bad next state. - int start = 0; - if (negative(transition_index(indy, next, start))) return false; - // no transition exists for that next state. - MOVE_STATE(m, next, state_machine::SIMPLE, 0); - int trig = m.update(); - if (trig) return pulse(m, trig); - return true; -} - -bool transition_map::pulse(state_machine &m, int trigger) -{ - if (!valid()) return false; // this is not operational yet! - CHECK_VALID(m); -#ifdef DEBUG_STATE_MACHINE - if (negative(state_index(m._current))) - LOG(astring("(%s) transition_map::pulse: bad logic error; state is missing.", m._name->s())); -#endif - FIND_STATE(m._current) false; // logic error! - state_info &found = (*_state_list)[indy]; - for (int i = 0; i < found.transitions.length(); i++) { - if (found.transitions[i].type == transition_info::RANGE) { - // found the right type of transition. - transition_info &tran = found.transitions[i]; - if ( (tran.low_trigger <= trigger) - && (tran.high_trigger >= trigger) ) { - // found a transition with an acceptable range. - MOVE_STATE(m, tran.next_state, state_machine::RANGE, trigger); - int trig = m.update(); - if (trig) return pulse(m, trig); - return true; - } - } - } - return false; -} - -bool transition_map::time_slice(state_machine &m) -{ - if (!valid()) return false; // this is not operational yet! - CHECK_VALID(m); - -#ifdef DEBUG_STATE_MACHINE - if (negative(state_index(m._current))) - LOG(astring("(%s) transition_map::time_slice: bad logic error; state " - "is missing.", m._name->s())); -#endif - FIND_STATE(m._current) false; // logic error! - - state_info &found = (*_state_list)[indy]; - for (int i = 0; i < found.transitions.length(); i++) { - if (found.transitions[i].type == transition_info::TIMED) { - // found the right type of transition. - transition_info &tran = found.transitions[i]; - int duration = tran.time_span; - int override = m.duration_override(m._current, tran.next_state); - if (override) duration = override; - if (*m._start < time_stamp(-duration)) { - // found a transition with an expired time. - MOVE_STATE(m, tran.next_state, state_machine::TIMED, 0); - int trig = m.update(); - if (trig) return pulse(m, trig); - return true; - } - } - } - return false; -} - -bool transition_map::reset(state_machine &m) -{ - if (!valid()) return false; // this is not operational yet! - m._current = _start_state; - m._last = _start_state; - m._trig = 0; - m._type = state_machine::SIMPLE; - m._start->reset(); - return true; -} - -bool transition_map::check_overlapping(int &examine) -{ - FUNCDEF("check_overlapping"); - examine = 0; - for (int i = 0; i < states(); i++) { - examine = i; - for (int j = 0; j < (*_state_list)[i].transitions.length(); j++) { - transition_info &a = (*_state_list)[i].transitions[j]; - if (a.type != transition_info::RANGE) continue; - for (int k = j + 1; k < (*_state_list)[i].transitions.length(); k++) { - transition_info &b = (*_state_list)[i].transitions[k]; - if (b.type != transition_info::RANGE) continue; - if ( (b.low_trigger <= a.low_trigger) - && (b.high_trigger >= a.low_trigger) ) { - LOG(astring("intersecting range on low end!")); - return false; - } - if ( (b.low_trigger <= a.high_trigger) - && (b.high_trigger >= a.high_trigger) ) { - LOG(astring("intersecting range on high end!")); - return false; - } - } - } - } - return true; -} - -bool transition_map::check_reachability(int &examine) -{ - FUNCDEF("check_reachability"); - examine = 0; - - // list_to_add: the list of states that are definitely reachable and that - // need to be considered. - int_array list_to_add; - list_to_add += _start_state; - - // already_checked: those states that have already been completely considered - // as to their reachability. - array already_checked(states()); - for (int i = 0; i < already_checked.length(); i++) - already_checked[i] = false; - - while (list_to_add.length()) { - // the next state that IS reachable has all of the states reachable from - // it added to the list of states that are to be checked... - examine = list_to_add[0]; - int indy = state_index(examine); - if (negative(indy)) { - LOG(a_sprintf("bad state index for state %d!", examine)); - return false; - } -#ifdef DEBUG_STATE_MACHINE - LOG(a_sprintf("state to add is %d, list size=%d.", examine, - list_to_add.length())); -#endif - // delete the item that we are now checking. - list_to_add.zap(0, 0); - - // check whether this item has already had its kids (those states reachable - // from it) added to the list to add. if so, skip it. - if (already_checked[indy]) continue; - - // update the information for this state we are considering in the list of - // already checked states. - already_checked[indy] = true; - - state_info &found = (*_state_list)[indy]; - - // all states this one can reach are added to the list to check. - for (int i = 0; i < found.transitions.length(); i++) { -#ifdef DEBUG_STATE_MACHINE - LOG(astring("checking transition %d: ", i)); -#endif - int now_reachable = found.transitions[i].next_state; -#ifdef DEBUG_STATE_MACHINE - LOG(astring("now reaching %d.", now_reachable)); -#endif - if (now_reachable == examine) continue; - else { - int indy = state_index(now_reachable); - if (already_checked[indy]) continue; - } - list_to_add += now_reachable; - } - } -#ifdef DEBUG_STATE_MACHINE - LOG("done checking reachability."); -#endif - for (int j = 0; j < already_checked.length(); j++) - if (!already_checked.get(j)) { - examine = (*_state_list)[j].state_id; - LOG(a_sprintf("state %d is not reachable", examine)); - return false; - } - return true; // all states are reachable. -} - -} //namespace. - diff --git a/core/library/processes/state_machine.h b/core/library/processes/state_machine.h deleted file mode 100644 index 3d2761e8..00000000 --- a/core/library/processes/state_machine.h +++ /dev/null @@ -1,348 +0,0 @@ -#ifndef STATE_MACHINE_CLASS -#define STATE_MACHINE_CLASS - -/*****************************************************************************\ -* * -* Name : state_machine * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace processes { - -class state_machine_override_array; -class state_machine_state_array; - -//! Monitors objects with multiple states and the transitions between states. -/*! - A state machine is a computational abstraction for any control process - that has discrete states and transitions between the states. - As used here, the "state_machine" is a base class for objects that can be - manipulated by the "transition_map" class. Your transition map must be - validated and you must use it to reset() your state_machine object before - any activity can occur. (One aspect of validation is that all states must be - reachable from the user-specified starting state. The other requirements - are documented below for transition_map.) Once validation is done, the - transition map manages the machine through the various states that are - defined and applies trigger values to modify the current state when asked - to do so. It also applies any defined timed transitions automatically. - This implementation of state machines is configured once (by defining the - transition_map object), but then the machines can be run repeatedly (via - the state_machine object). -*/ - -class state_machine : public virtual basis::root_object -{ -public: - state_machine(); - //!< sets up the machine in a blank state. - - state_machine(const state_machine &to_copy); - //!< copies the machine provided in "to_copy". - - virtual ~state_machine(); - - DEFINE_CLASS_NAME("state_machine"); - - state_machine &operator =(const state_machine &to_copy); - //!< assigns this to a copy of the machine provided in "to_copy". - - virtual int update(); - //!< this is the main implementation function provided by derived classes. - /*!< this is implemented by derived classes that want to perform an action - when the state machine is pulsed (see below in transition_map), which is - why it is not pure virtual; a state machine might still be useful as a - state tracking object rather than needing to implement extended - functionality. this function is invoked whenever the state changes due to - a timed, range based or simple transition. one must be careful when - servicing this transition not to enmesh oneself in a snarled invocation - situation; if make_transition or time_slice or pulse are invoked from this - update function and conditions are right for another transition, then the - update function will be re-entered recursively. if the value returned - from update is non-zero, it is used to pulse the state machine again as a - new trigger value (this is safer than explicitly calling one of the - transition causing functions). note that this assumes that zero is an - invalid trigger. if your triggers include zero as a valid value, don't - try returning it through update; use pulse to force the triggering to - accept the invalid trigger instead. */ - - int current() const { return _current; } - //!< returns the current state. - /*!< NOTE: a zero value for a state means that it is uninitialized. */ - - int last() const { return _last; } - //!< returns the last active state. - - int trigger() const { return _trig; } - //!< returns the trigger that caused this state. - /*!< this is only meaningful when the transition was caused by a range - transition. if it was, then ranged() will return true. */ - - enum transition_types { SIMPLE, RANGE, TIMED }; - //!< the three types of transitions supported. - - bool simple() const { return _type == SIMPLE; } - //!< returns true if the last transition was a simple type. - bool ranged() const { return _type == RANGE; } - //!< returns true if the last transition was a ranged type. - bool timed() const { return _type == TIMED; } - //!< returns true if the last transition was a timed type. - - void set_state(int new_current, int new_last, int trigger, - transition_types type); - //!< sets the current state to "new_current" and the previous state to "new_last". - /*!< the "trigger" causing the transition, if any, will be stored also. - the "type" of transition is stored also. the time stamp for time spent in - the current state is reset. be very careful with this; if the two states - do not conform to the legal transitions in your map, then the state - machine will not behave properly. */ - - timely::time_stamp start() const; - //!< start time for the current state. - /*!< this records how long we've been in this state. */ - - void set_name(const basis::astring &name); - //!< sets the name to be printed in any debugging information for "this". - - basis::astring get_name() const; - //!< retrieves the object's current name. - - void override_timing(int current, int next, int duration); - //!< modifies the recorded timing for timed transitions. - /*!< this overrides the transition_map's time-out value for the transition - between the states "current" and "next". a time-out of "duration" - will be used instead of whatever was specified when the map was set - up. to erase an override, use zero as the "duration". */ - - int duration_override(int current, int next) const; - //!< reports if there's a duration override for a timed transition. - /*!< this returns the amount of time that this particular state_machine is - allowed to exist in the "current" state before progressing to the "next" - state. this has nothing to do with the transition_map; it is valid only - for this state_machine object. zero is returned if no override exists. */ - -private: - friend class transition_map; - //!< manager buddy object. - int _current; //!< the current state of the state machine. - int _last; //!< the previous state for the state machine. - int _trig; //!< the last trigger value, if _ranged is true. - transition_types _type; //!< what kind of transition just occurred. - timely::time_stamp *_start; //!< the time this state started. - basis::astring *_name; //!< the name this state_machine reports itself as. - state_machine_override_array *_overrides; //!< the timing overrides. - - int duration_index(int current, int next) const; - //!< finds the index of a duration override in our list. - /*!< this locates the duration override specified for the transition - between "current" and "next". it returns the index of that override, or - a negative number if the override is not found. */ -}; - -////////////// - -//! The transition_map manages state machines and causes state changes to occur. -/*! - The transition_map is a heavyweight class that is a repository for all - information about transitions and which manages pushing the state machines - through the proper states. - - The transition_map guarantees these characteristics: - - 0) the below characteristics are checked (in validate) and no state - machine object is allowed to operate until they are satisfied, - - 1) the machine starts in the specified starting state, - - 2) the current state is always one that has been added and approved, - - 3) transitions are allowed between states only if the transition has - been added and approved, - - 4) the update() function is invoked every time the machine hits a - transition between states (even if it is a transition to the same - state), - - 5) range transitions are non-intersecting within one state, -*** 5 is unimplemented *** - - 6) all states are reachable from the starting state by some valid - transition, and - - 7) each state has no more than one timed transition. - - if any of these conditions are violated, then validate() will fail. - the machine will also not operate properly (at all) until the conditions - are satisfied by validate(). the states and transitions should - thus be carefully examined before turning them into a state machine.... -*/ - -class transition_map : public virtual basis::root_object -{ -public: - transition_map(); - virtual ~transition_map(); - - // informational functions... - - DEFINE_CLASS_NAME("transition_map"); - - bool valid() const { return _valid; } - //!< returns true if the transition_map is valid and ready for operation. - /*!< once the validate() call has succeeded and valid() is true, no more - configuration functions (see below) may be called until the reconfigure() - function is invoked. */ - - int states() const; - //!< returns the current number of states. - - // validation functions... - - enum outcomes { - OKAY = basis::common::OKAY, - DEFINE_OUTCOME(BAD_START, -49, "The start state has not been properly " - "specified"), - DEFINE_OUTCOME(OVERLAPPING_RANGES, -50, "The ranges overlap for two " - "transitions from a state"), - DEFINE_OUTCOME(UNREACHABLE, -51, "There is an unreachable state in the map") - }; - basis::outcome validate(int &examine); - //!< checks to that all required transition conditions are satisfied. - /*!< OKAY is returned if they are and the map is then ready to operate. - other outcomes are returned if one or more of the conditions are not met: - BAD_START means that the starting state is badly specified. - OVERLAPPING_RANGES means that one state has two transitions that do not - have mutually exclusive ranges. UNREACHABLE means that a state is not - reachable from the starting state. for all of these cases, the "examine" - parameter is set to a state related to the problem. */ - - void reconfigure(); - //!< puts the transition_map back into an unvalidated state. - /*!< this allows the characteristics of the map to be changed. validate() - must be called again before resuming operation using this map. */ - - // configuration functions... - - // NOTE: all of the functions below will fail if the transition_map has - // already been validated previously and reconfigure() has not been called. - - // NOTE: a zero value for a state means that it is uninitialized. thus, zero - // is never allowed as a value for a state or a transition endpoint. zero is - // grudgingly allowed as a trigger value, although that interferes with the - // state_machine object's update() method. - - bool add_state(int state_number); - //!< registers a legal state in the transition_map. - /*!< no action will be taken for any state until it is registered. the - operation returns true for successful registration and false for errors - (such as when a state is already registered or is invalid). */ - - bool set_start(int starting_state); - //!< assigns a state as the first state. - /*!< if the "starting_state" does not already exist, it is an error and - false is returned. */ - - bool add_simple_transition(int current, int next); - //!< adds a transition between "current" and "next". - /*!< it is an error to use either an invalid "current" state or an invalid - "next" state. these errors are detected during the validate() function. - this type of transition is unspecialized--it does not respond to triggers - and does not occur due to timing. to make a state_machine undergo this - transition, make_transition() must be used. */ - - bool add_range_transition(int current, int next, int low, int high); - //!< adds a transition that listens to triggers in the pulse() method. - /*!< this uses "low" and "high" as the inclusive range of triggers that - will cause a transition to the "next" state when a state_machine is pulsed - in the "current" state. it is erroneous for these trigger values to - intersect with other values in the same "current" state. */ - bool add_timed_transition(int current, int next, int duration); - //!< adds a transition that occurs after a timeout with no other activity. - /*!< adds a timed transition to the "next" state when the "current" state - has lasted "duration" milliseconds. this relies on the time_slice function - being called periodically, with appropriate regularity for the specified - "duration" (e.g., if one's time-out is 100 ms, then one might want to call - time_slice() every 20 ms or so to ensure that the transition is at most 20 - ms late in firing. NOTE: this is not an argument for calling time_slice() - as fast as possible; it is an argument for realizing that the "duration" - must be carefully considered to meet one's timing deadlines). */ - - // transition functions... - - // NOTE: all of these actions will fail if the transition_map has not been - // validated yet. - - bool make_transition(state_machine &m, int next); - //!< changes the state to the "next" listed for "m" given the current state. - /*!< it is an error to make a transition that has not been specified - through an add_X() transition function (false is returned). if the - transition succeeds, then the current_state will be "next". */ - - bool pulse(state_machine &m, int trigger); - //!< applies a "trigger" to possibly cause a range transition. - /*!< this causes the state_machine to accept the "trigger" as input and - perform at least one traversal of the transition_map. if the trigger - value is found in one of the range transitions for the current state, then - the next state specified in that transition becomes the current state and - the update() function is invoked (and true is returned). if the trigger - is not found in any range transition, then false is returned. */ - - bool time_slice(state_machine &m); - // allows the transition_map to process any timed transitions that may be - // required for the state_machine "m". the return value is true if such - // a transition was found. - - bool reset(state_machine &m); - // resets the state_machine to the starting state in the transition_map. - // the update function is NOT invoked at the time of the reset. true is - // returned if the reset was successful; it would fail if the transition - // map has not been validated yet. - -private: - bool _valid; //!< records whether we've been validated or not. - int _start_state; //!< remembers the default starting state. - state_machine_state_array *_state_list; - //!< the list of transitions between states. - - bool check_overlapping(int &examine); - //!< a validation function that looks for erroneous range transitions. - /*!< this returns true if there are no overlapping ranges found in the - range transitions for each state. */ - - bool check_reachability(int &examine); - //!< returns true if all states are reachable from the starting state. - - int state_index(int state_id) const; - //!< returns the index of "state_id" in states, if it exists. - - int transition_index(int state_index, int next, int &start); - //!< locates a transition into "next" for a state in our list. - /*!< returns the index of a transition between the state at "state_index" - and the "next" state by starting the search at index "start". if there - is no matching transition from the "start" index on in the transition - list, then a negative number is returned. "start" is updated to point - to the index just after a found transition, or to some random number if - the index is not found. */ - - bool check_states(); - //!< returns false if the current machine is hosed up. - - // disallowed functions for now: - transition_map(const transition_map &); - transition_map &operator =(const transition_map &); -}; - -} //namespace. - -#endif - diff --git a/core/library/processes/thread_cabinet.cpp b/core/library/processes/thread_cabinet.cpp deleted file mode 100644 index 19bc679f..00000000 --- a/core/library/processes/thread_cabinet.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/*****************************************************************************\ -* * -* Name : thread_cabinet * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "ethread.h" -#include "thread_cabinet.h" - -#include -#include -#include -#include - -#undef LOCKIT -#define LOCKIT auto_synchronizer l(*_lock) - // grabs the mutex for access to the list. - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -//#define DEBUG_THREAD_CABINET - // uncomment for noisier version. - -using namespace basis; -using namespace loggers; -using namespace structures; -using namespace timely; - -namespace processes { - -class thread_record -{ -public: - ethread *_thread; - unique_int _id; - - thread_record(const unique_int &id, ethread *t) - : _thread(t), _id(id) {} - - ~thread_record() { - _thread->stop(); - WHACK(_thread); - } -}; - -////////////// - -class thread_amorph : public amorph -{ -public: -}; - -////////////// - -thread_cabinet::thread_cabinet() -: _lock(new mutex), - _threads(new thread_amorph), - _next_id(new int_roller(1, MAXINT32 - 1)), - _no_additions(0) -{ -} - -thread_cabinet::~thread_cabinet() -{ - WHACK(_threads); - WHACK(_lock); - WHACK(_next_id); -} - -int thread_cabinet::threads() const { return _threads->elements(); } - -unique_int thread_cabinet::add_thread(ethread *to_add, bool start_it, - void *parm) -{ -#ifdef DEBUG_THREAD_CABINET - FUNCDEF("add_thread"); -#endif - LOCKIT; - if (_no_additions) { -#ifdef DEBUG_THREAD_CABINET - LOG("no additions flag is true; destroying the thread and failing out."); -#endif - // can't just leave it unhooked hanging out in space... - WHACK(to_add); - return 0; - } - int use_id = _next_id->next_id(); - if (start_it) { - to_add->start(parm); - } else { -#ifdef DEBUG_THREAD_CABINET - if (to_add->thread_finished()) - LOG(a_sprintf("thread %x is not going to be started and it " - "hasn't started yet!", to_add)); -#endif - } - _threads->append(new thread_record(use_id, to_add)); - return use_id; -} - -bool thread_cabinet::any_running() const -{ - LOCKIT; - for (int i = 0; i < _threads->elements(); i++) { - if (_threads->borrow(i)->_thread->thread_started()) return true; - } - return false; -} - -void thread_cabinet::start_all(void *ptr) -{ - LOCKIT; - for (int i = 0; i < _threads->elements(); i++) { - if (_threads->borrow(i)->_thread->thread_finished()) { - _threads->borrow(i)->_thread->start(ptr); - } - } -} - -void thread_cabinet::cancel_all() -{ - FUNCDEF("cancel_all"); - { - LOCKIT; // short lock. - _no_additions++; // keep people from adding new threads. - for (int i = 0; i < _threads->elements(); i++) { - _threads->borrow(i)->_thread->cancel(); - } - } - LOCKIT; - _no_additions--; // allow new threads again. - if (_no_additions < 0) - continuable_error(class_name(), func, "error in logic regarding " - "no additions."); -} - -void thread_cabinet::stop_all() -{ - FUNCDEF("stop_all"); - { - LOCKIT; // short lock. - _no_additions++; // keep people from adding new threads. - } - cancel_all(); // signal all threads to leave. - // pause to give them a little while to leave. - time_control::sleep_ms(20); - while (true) { - LOCKIT; // short lock. - if (!_threads->elements()) break; // done; nothing left. - clean_debris(); // remove any that did stop. - time_control::sleep_ms(20); // snooze for a short while. - } - LOCKIT; - _no_additions--; // allow new threads again. - if (_no_additions < 0) - continuable_error(class_name(), func, "error in logic regarding " - "no additions."); -} - -bool thread_cabinet::zap_thread(const unique_int &to_whack) -{ - LOCKIT; - for (int i = 0; i < _threads->elements(); i++) { - if (_threads->borrow(i)->_id == to_whack) { - // this is the one they want zapped. - _threads->zap(i, i); - return true; - } - } - return false; -} - -bool thread_cabinet::cancel_thread(const unique_int &to_cancel) -{ - LOCKIT; - for (int i = 0; i < _threads->elements(); i++) { - if (_threads->borrow(i)->_id == to_cancel) { - // this is the one to signal regarding its own demise. - _threads->borrow(i)->_thread->cancel(); - return true; - } - } - return false; -} - -void thread_cabinet::clean_debris() -{ -#ifdef DEBUG_THREAD_CABINET - FUNCDEF("clean_debris"); -#endif - LOCKIT; - for (int i = 0; i < _threads->elements(); i++) { - if (_threads->borrow(i)->_thread->thread_finished()) { - // this one's no longer doing anything. -#ifdef DEBUG_THREAD_CABINET - LOG(a_sprintf("clearing thread %x out.", _threads->borrow(i)->_thread)); -#endif - _threads->zap(i, i); - i--; // skip back before the whack. - } - } -} - -int_set thread_cabinet::thread_ids() const -{ - LOCKIT; - int_set to_return; - for (int i = 0; i < _threads->elements(); i++) - to_return += _threads->borrow(i)->_id.raw_id(); - return to_return; -} - -ethread *thread_cabinet::get_thread(int index) -{ - LOCKIT; - thread_record *rec = _threads->borrow(index); - if (rec) return rec->_thread; - return NIL; -} - -} //namespace. - diff --git a/core/library/processes/thread_cabinet.h b/core/library/processes/thread_cabinet.h deleted file mode 100644 index f8e94e73..00000000 --- a/core/library/processes/thread_cabinet.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef THREAD_CABINET_CLASS -#define THREAD_CABINET_CLASS - -/*****************************************************************************\ -* * -* Name : thread_cabinet * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -// forward. -#include "ethread.h" - -#include -#include -#include -#include - -namespace processes { - -class thread_amorph; - -//! Manages a collection of threads. -/*! - The thread cabinet allows one to corral a bunch of threads in one place - and treat them as a group if necessary. -*/ - -class thread_cabinet -{ -public: - thread_cabinet(); - virtual ~thread_cabinet(); - - DEFINE_CLASS_NAME("thread_cabinet"); - - structures::unique_int add_thread(ethread *to_add, bool start_it, void *parm); - //!< adds a thread to be managed by the thread_cabinet. - /*!< the thread cabinet takes over responsibility for the thread "to_add". - if "start_it" is true, then the thread is started. otherwise it is - left in whatever state it was in. the "parm" is passed to the thread's - start() method. */ - - bool zap_thread(const structures::unique_int &to_whack); - //!< removes the thread with the id "to_whack". - /*!< if it's found and stopped and removed, true is returned. note that - if the thread is found, then this will wait until the thread exits before - whacking it. */ - - bool cancel_thread(const structures::unique_int &to_cancel); - //!< shuts down the thread "to_cancel" as quickly as possible. - /*!< this calls the cancel() method on the thread "to_cancel", which tells - the thread to stop as soon as possible. once it has stopped, the - clean_debris() method will throw it and other stopped threads out. */ - - int threads() const; //!< number of threads being managed here. - - structures::int_set thread_ids() const; - //!< returns the identifiers of all threads managed by this object. - - bool any_running() const; - //!< returns true if any threads are currently running. - - void start_all(void *pointer); - //!< cranks up any threads that are not already running. - /*!< the "pointer" will be provided to any threads that are started. */ - - void cancel_all(); - //!< signals to all threads that they should exit as soon as possible. - /*!< this does not actually wait for them to exit. */ - - void stop_all(); - //!< makes all of the threads quit. - /*!< they are cleaned up after they have stopped running also. any - attempts to add threads while this method is operating will be rejected. */ - - ethread *get_thread(int index); - //!< this returns the thread at "index" in our list. - /*!< note that this is not safe to use if other threads could be removing - threads from the cabinet or calling clean_debris(). */ - - void clean_debris(); - //!< clean out threads that have finished. - /*!< note that if threads were added to the list without starting them, - then these get cleaned out also. */ - -private: - basis::mutex *_lock; //!< synchronizes our thread list. - thread_amorph *_threads; //!< the list of threads we're managing. - structures::int_roller *_next_id; //!< the id to be assigned to the next thread. - int _no_additions; //!< true if we're not allowed to add threads currently. -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/amorph.h b/core/library/structures/amorph.h deleted file mode 100644 index d2104c70..00000000 --- a/core/library/structures/amorph.h +++ /dev/null @@ -1,520 +0,0 @@ -#ifndef AMORPH_CLASS -#define AMORPH_CLASS - -/*****************************************************************************\ -* * -* Name : amorph * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "object_packers.h" - -#include -#include -#include -#include - -//! A dynamic container class that holds any kind of object via pointers. -/*! - The object is considered owned by the amorph unless re-acquired from it, - and will be deleted when the amorph is destroyed. - - An amorph has a specified number of fields at any one time, but the number - can be changed with "zap", "insert" and "adjust". Fields in the amorph - are either full or empty, where an empty field in the amorph has NIL as - its content. "put" adds a new field to the amorph. "get" retrieves the - contents of a field as a constant. "acquire" is used to check a field - out of the amorph, meaning that the amorph no longer possesses that field. - The legal range of indices in an amorph is from 0 through - "elements() - 1". In general, a range violation for an index is - treated as an invalid request and is ignored (although BAD_INDEX is - returned by functions with compatible return values). - - Model: - - The policy followed in amorph is that once an object is checked in with - "put" or "append", then the amorph owns that object. The object must not - be destroyed externally, because the amorph will automatically destroy - the object when either: 1) the amorph itself is destroyed, or 2) another - object is entered into the same field. If the stored object must be - destroyed externally, then it should be checked out of the amorph with - "acquire"; after that, the pointer may be deleted. "get" and "borrow" - return a pointer to the stored item while leaving it checked in to the - amorph. it is safe to modify what was "borrow"ed or to look at what one - "get"s, but do not destroy the pointers returned from either method. -*/ - -namespace structures { - -template -class amorph : private basis::array -{ -public: - amorph(int elements = 0); - //!< constructs an amorph capable of holding "elements" pointers. - - ~amorph(); - - int elements() const { return this->length(); } - //!< the maximum number of elements currently allowed in this amorph. - - int valid_fields() const { return _fields_used; } - //!< Returns the number of fields that have non-NIL contents. - /*!< This might be different from the number of total elements. */ - - void adjust(int new_max); - //!< Changes the maximum number of elements for this amorph. - /*!< If the new number is smaller than the original, then the fields at - index "new_maximum" and upwards are thrown away. existing fields are - kept. */ - - void reset(int new_maximum = 0); - //!< like adjust but doesn't keep existing contents. - - basis::outcome put(int field, const contents *data); - //!< Enters an object into the field at index "field" in the amorph. - /*!< If "data" is NIL, then the field is cleared. The amorph considers the - pointer "data" to be its own property after put is invoked; "data" - should not be destructed since the amorph will automatically do so. - This restriction does not hold if the object is checked back out of - the amorph with acquire(). */ - - basis::outcome append(const contents *data); - //!< puts "data" on the end of this amorph. - /*!< adds an element to the end of the amorph by increasing the amorph size - (with "adjust") and putting the element into the new spot (with "put"). */ - - basis::outcome operator += (const contents *data) { return append(data); } - //!< a synonym for append. - - //! Returns a constant pointer to the information at the index "field". - /*! If no information is stored or the field is out range, then NIL is - returned. */ - const contents *get(int field) const; - //! Returns a pointer to the information at the index "field". - /*! Also returns NIL for invalid indexes. DO NOT destroy the returned - pointer; it is still owned by the amorph. */ - contents *borrow(int field); - - //! synonym for get. - const contents *operator [] (int field) const { return get(field); } - //! synonym for borrow. - contents *operator [] (int field) { return borrow(field); } - - contents *acquire(int field); - //!< Retrieves a "field" from the amorph, taking responsibility for it back. - /*!< This function is similar to get, except that the contents are pulled - out of the amorph. The contents will no longer be destroyed when the - amorph is destroyed. To store the modified contents again, use put, - and then the amorph will take over management of the contents again. - Note that the index is not zapped with this function; the acquired - "field" will simply hold a null pointer. */ - - basis::outcome clear(int field); - //!< Clears the contents of the field specified. - /*!< Clearing an empty field has no effect. Clearing an invalid field has - no effect. NOTE: clearing the contents means that the contents are - destroyed, not just disconnected. */ - - void clear_all(); - //!< Clears every field in the amorph. - - basis::outcome zap(int start, int end); - //!< Removes a range of indices from the amorph. - /*!< This does not just clear the field associated with the specified index - as "clear" does, it actually changes the number of total elements by - removing the indices from the amorph. The new amorph contains the old - elements up to just before the "start" and from the "end" + 1 through the - end of the amorph. AMORPH_BAD_INDEX is returned if either index is out of - range. If the zap succeeds, then AMORPH_OKAY is returned, even if the - "end" is less than the "start". */ - - basis::outcome insert(int position, int lines_to_add); - //!< Adds "lines_to_add" indices to the amorph at the index "position". - /*!< If "lines_to_add" is non-positive, the request is ignored. Inserting - at a position beyond the bounds of the array is ignored, but a position AT - elements() is allowed (it is an append...). */ - - int find_empty(basis::outcome &o) const; - //!< Returns the index of a free field if there are any. - /*!< The returned index is invalid if the "o" is IS_FULL. */ - - const contents *next_valid(int &field) const; - //!< Returns the contents of the next valid element at or after "field". - /*!< "field" is set to the location where an entry was found, if one was - actually found. If none exists at "field" or after it, then NIL is - returned. */ - - int find(const contents *to_locate, basis::outcome &o); - //!< Searches the amorph for the contents specified. - /*!< Since only pointers to the contents are maintained, the search is - based on finding a pointer in the amorph that is identical to "to_locate". - if "o" is OKAY, then the index of the entry is returned. If - "o" is NOT_FOUND, then the contents are not present. */ - - void swap_contents(amorph &other); - //!< Exchanges the contents of "this" and "other". - /*!< No validation is performed but this should always succeed given - amorphs that are constructed properly. */ - -private: - int _fields_used; //!< The number of fields currently full of info. - - void check_fields(const char *where) const; - //!< Crashes out if the field count is wrong. - /*!< This is only used for the debugging version. */ - - void set_nil(int start, int end); - // Puts NIL in the indices between start and end. - /*!< Does not delete whatever used to reside there; it just sets the - pointers to NIL. */ - - // not to be used: amorphs should not be copied because it is intended that - // they support storing heavyweight objects that either have no copy - // constructors or have high-cost copy constructors. - amorph(const amorph &to_copy) {} //!< not to be used. - amorph &operator = (const amorph &to_copy) { return *this; } - //!< not to be used. -}; - -////////////// - -// these extensions are phrased as macros to avoid including the headers -// necessary for compiling them. to use them, just put the macro name in -// the file where the template is needed. - -////////////// - -//! This can be used when the templated object has a copy constructor. -/*! this makes the "to_assign" into a copy of the "to_copy" amorph. */ -template -void amorph_assign(amorph &to_assign, - const amorph &to_copy); - -////////////// - -//! support for packing an amorph into an array of bytes. -/*! - this can be used when the underlying object is based on packable or - supports the same pack/unpack methods. note that if there are empty spots in - the amorph, they are not packed. when the packed form is unpacked again, it will - have only the first N elements set, where N is the number of non-empty items. -*/ -template -void amorph_pack(basis::byte_array &packed_form, const amorph &to_pack); - -//! unpacks the amorph from an array of bytes. -template -bool amorph_unpack(basis::byte_array &packed_form, amorph &to_unpack); - -//! reports how large the packed form will be. -template -int amorph_packed_size(const amorph &to_pack); - -// implementation for longer methods... - -//#define DEBUG_AMORPH - // uncomment to enable more testing, as well as complaints on errors. - -#undef static_class_name -#define static_class_name() "amorph" - // used in bounds_halt macro. - -#undef AMO_ALERT -#ifdef DEBUG_AMORPH - #include - #define AMO_ALERT(a, b, c) basis::throw_error(basis::astring(a), basis::astring(func), basis::astring(b) + " -- " + c) - #define CHECK_FIELDS check_fields(func) -#else - #define AMO_ALERT(a1, a2, a3) {} - #define CHECK_FIELDS { if (!func) {} } -#endif - -////////////// - -template -amorph::amorph(int elements) -: basis::array(elements, NIL, basis::array::SIMPLE_COPY - | basis::array::EXPONE | basis::array::FLUSH_INVISIBLE), - _fields_used(0) -{ - FUNCDEF("constructor"); - set_nil(0, elements - 1); - CHECK_FIELDS; -} - -template -amorph::~amorph() -{ - FUNCDEF("destructor"); - CHECK_FIELDS; - clear_all(); -} - -template -void amorph::set_nil(int start, int end) -{ - for (int i = start; i <= end; i++) - basis::array::put(i, (contents *)NIL); -} - -template -void amorph::check_fields(const char *where) const -{ - FUNCDEF("check_fields"); - int counter = 0; - for (int i = 0; i < elements(); i++) - if (basis::array::get(i)) counter++; - if (_fields_used != counter) { - AMO_ALERT("amorph", basis::a_sprintf("check_fields for %s", where), - "error in _fields_used count"); - } -} - -template -void amorph::reset(int new_maximum) -{ - FUNCDEF("reset"); - CHECK_FIELDS; - adjust(new_maximum); - clear_all(); -} - -template -void amorph::clear_all() -{ - FUNCDEF("clear_all"); - CHECK_FIELDS; - for (int i = 0; i < elements(); i++) clear(i); -} - -template -basis::outcome amorph::append(const contents *data) -{ - FUNCDEF("append"); - CHECK_FIELDS; - adjust(elements() + 1); - return put(elements() - 1, (contents *)data); -} - -template -const contents *amorph::get(int field) const -{ - FUNCDEF("get"); - CHECK_FIELDS; - bounds_return(field, 0, elements() - 1, NIL); - return basis::array::observe()[field]; -} - -template -void amorph::adjust(int new_maximum) -{ - FUNCDEF("adjust"); - CHECK_FIELDS; - if (new_maximum < 0) return; // bad input here. - int old_max = elements(); - if (new_maximum == old_max) return; // nothing to do. - if (new_maximum < old_max) { - // removes the elements beyond the new size of the amorph. - zap(new_maximum, old_max - 1); - // we're done tuning it. - return; - } - - // we get to here if we need more space than we used to. - int new_fields = new_maximum - old_max; - - basis::array::insert(old_max, new_fields); - for (int i = old_max; i < new_maximum; i++) { - basis::array::put(i, NIL); - } -} - -template -basis::outcome amorph::insert(int position, int lines_to_add) -{ - FUNCDEF("insert"); - CHECK_FIELDS; - bounds_return(position, 0, elements(), basis::common::OUT_OF_RANGE); - basis::outcome o = basis::array::insert(position, lines_to_add); - if (o != basis::common::OKAY) return basis::common::OUT_OF_RANGE; - set_nil(position, position + lines_to_add - 1); - return basis::common::OKAY; -} - -template -basis::outcome amorph::zap(int start_index, int end_index) -{ - FUNCDEF("zap"); - CHECK_FIELDS; - bounds_return(start_index, 0, elements() - 1, basis::common::OUT_OF_RANGE); - bounds_return(end_index, 0, elements() - 1, basis::common::OUT_OF_RANGE); - if (end_index < start_index) return basis::common::OKAY; - for (int i = start_index; i <= end_index; i++) clear(i); - basis::outcome o = basis::array::zap(start_index, end_index); - return (o == basis::common::OKAY? basis::common::OKAY : basis::common::OUT_OF_RANGE); -} - -template -basis::outcome amorph::put(int field, const contents *data) -{ - FUNCDEF("put"); - CHECK_FIELDS; - bounds_return(field, 0, elements() - 1, basis::common::OUT_OF_RANGE); - contents *to_whack = acquire(field); - WHACK(to_whack); - if (data) { - basis::array::access()[field] = (contents *)data; - _fields_used++; - } - return basis::common::OKAY; -} - -template -int amorph::find_empty(basis::outcome &o) const -{ - FUNCDEF("find_empty"); - CHECK_FIELDS; - if (_fields_used == elements()) { o = basis::common::IS_FULL; return 0; } - for (int i = 0; i < elements(); i++) - if (!basis::array::get(i)) { o = basis::common::OKAY; return i; } - AMO_ALERT("amorph", "empty", "_fields_used is incorrect"); - return basis::common::IS_FULL; -} - -template -const contents *amorph::next_valid(int &field) const -{ - FUNCDEF("next_valid"); - CHECK_FIELDS; - bounds_return(field, 0, elements() - 1, NIL); - for (int i = field; i < elements(); i++) - if (basis::array::get(i)) { - field = i; - return basis::array::get(i); - } - return NIL; -} - -template -basis::outcome amorph::clear(int field) -{ - FUNCDEF("clear"); - CHECK_FIELDS; - return this->put(field, NIL); -} - -template -contents *amorph::acquire(int field) -{ - FUNCDEF("acquire"); - CHECK_FIELDS; - contents *to_return = borrow(field); - if (to_return) { - _fields_used--; - basis::array::access()[field] = NIL; - } - return to_return; -} - -template -int amorph::find(const contents *to_locate, basis::outcome &o) -{ - FUNCDEF("find"); - CHECK_FIELDS; - if (!_fields_used) { o = basis::common::NOT_FOUND; return 0; } - for (int i = 0; i < elements(); i++) { - if (basis::array::get(i) == to_locate) { - o = basis::common::OKAY; - return i; - } - } - o = basis::common::NOT_FOUND; - return 0; -} - -template -contents *amorph::borrow(int field) -{ - FUNCDEF("borrow"); - CHECK_FIELDS; - bounds_return(field, 0, elements() - 1, NIL); - return basis::array::access()[field]; -} - -template -void amorph::swap_contents(amorph &other) -{ - FUNCDEF("swap_contents"); - CHECK_FIELDS; - int hold_fields = _fields_used; - _fields_used = other._fields_used; - other._fields_used = hold_fields; - this->basis::array::swap_contents(other); -} - -template -int amorph_packed_size(const amorph &to_pack) -{ - int parts_size = 0; - for (int i = 0; i < to_pack.elements(); i++) { - const contents *current = to_pack.get(i); - if (current) parts_size += current->packed_size(); - } - return PACKED_SIZE_INT32 + parts_size; -} - -template -void amorph_pack(basis::byte_array &packed_form, const amorph &to_pack) -{ - structures::attach(packed_form, to_pack.elements()); - for (int i = 0; i < to_pack.elements(); i++) { - const contents *current = to_pack.get(i); - if (current) current->pack(packed_form); - } -} - -template -bool amorph_unpack(basis::byte_array &packed_form, amorph &to_unpack) -{ - to_unpack.reset(); - int elem = 0; - if (!structures::detach(packed_form, elem)) return false; - for (int i = 0; i < elem; i++) { - contents *to_add = new contents; - if (!to_add->unpack(packed_form)) { delete to_add; return false; } - to_unpack.append(to_add); - } - return true; -} - -template -void amorph_assign(amorph &to_assign, - const amorph &to_copy) -{ - if (&to_assign == &to_copy) return; - to_assign.clear_all(); - if (to_assign.elements() != to_copy.elements()) { - to_assign.zap(0, to_assign.elements() - 1); - to_assign.insert(0, to_copy.elements()); - } - for (int i = 0; i < to_assign.elements(); i++) { - if (to_copy.get(i)) to_assign.put(i, new contents(*to_copy.get(i))); - else to_assign.put(i, (contents *)NIL); - } -} - -#undef static_class_name - -} //namespace. - -#endif - diff --git a/core/library/structures/bit_vector.cpp b/core/library/structures/bit_vector.cpp deleted file mode 100644 index 8c00528e..00000000 --- a/core/library/structures/bit_vector.cpp +++ /dev/null @@ -1,317 +0,0 @@ -/*****************************************************************************\ -* * -* Name : bit_vector * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bit_vector.h" - -#include -#include -#include -#include - -//#define DEBUG_BIT_VECTOR - // uncomment this to get debugging noise. - -#undef LOG -#ifdef DEBUG_BIT_VECTOR - #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) -#else - #define LOG(s) {} -#endif - -using namespace basis; - -namespace structures { - -bit_vector::bit_vector() -: _implementation(new byte_array(0, NIL)), _number_of_bits(0) -{} - -bit_vector::bit_vector(int number_of_bits, const abyte *initial) -: _implementation(new byte_array(0, NIL)), _number_of_bits(0) -{ - reset(number_of_bits); - if (!initial) return; - _implementation->reset(number_of_packets(number_of_bits, - int(BITS_PER_BYTE)), initial); -} - -bit_vector::bit_vector(const bit_vector &to_copy) -: _implementation(new byte_array(*to_copy._implementation)), - _number_of_bits(to_copy._number_of_bits) -{} - -bit_vector::~bit_vector() { WHACK(_implementation); } - -bit_vector &bit_vector::operator = (const bit_vector &to_copy) -{ - if (this == &to_copy) return *this; - *_implementation = *to_copy._implementation; - _number_of_bits = to_copy._number_of_bits; - return *this; -} - -bit_vector::operator const byte_array & () const { return *_implementation; } - -int bit_vector::bits() const { return _number_of_bits; } - -bool bit_vector::whole() const { return negative(find_first(0)); } - -bool bit_vector::empty() const { return negative(find_first(1)); } - -bool bit_vector::operator == (const bit_vector &that) const -{ return compare(that, 0, _number_of_bits); } - -bool bit_vector::on(int position) const -{ return get_bit(into_two_dim(position)); } - -bool bit_vector::off(int position) const { return !on(position); } - -void bit_vector::resize(int number_of_bits) -{ -//printf("resize invoked, old size %d, new size %d...\n", bits(), number_of_bits); - if (negative(number_of_bits)) return; - if (bits() == number_of_bits) return; - int old_size = bits(); - _number_of_bits = number_of_bits; - int number_of_bytes = number_of_packets(number_of_bits, int(BITS_PER_BYTE)); - _implementation->resize(number_of_bytes); - // clear new space if the vector got larger. - if (old_size < number_of_bits) { - // lazy reset doesn't compute byte boundary, just does 8 bits. - for (int i = old_size; i < old_size + 8; i++) { - clear(i); -//printf("clearing bit %d.\n", i); - } - // clear the bytes remaining. - int old_bytes = number_of_packets(old_size + 8, int(BITS_PER_BYTE)); - for (int j = old_bytes; j < number_of_bytes; j++) { -//printf("clearing bit %d through %d.\n", j * 8, j * 8 + 7); - _implementation->use(j) = 0; - } - } -} - -void bit_vector::reset(int number_of_bits) -{ - resize(number_of_bits); - memset(_implementation->access(), 0, _implementation->length()); -} - -bit_vector::two_dim_location bit_vector::into_two_dim(int position) const -{ - two_dim_location to_return; - to_return.c_byte = position / BITS_PER_BYTE; - to_return.c_offset = position % BITS_PER_BYTE; - return to_return; -} - -bool bit_vector::get_bit(const two_dim_location &pos_in2) const -{ - bounds_return(pos_in2.c_byte * BITS_PER_BYTE + pos_in2.c_offset, 0, - _number_of_bits - 1, false); - abyte test_mask = abyte(1 << pos_in2.c_offset); - return TEST(abyte(_implementation->get(pos_in2.c_byte)), test_mask); -} - -void bit_vector::set_bit(int position, bool value) -{ - bounds_return(position, 0, bits() - 1, ); - set_bit(into_two_dim(position), value); -} - -void bit_vector::set_bit(const two_dim_location &pos_in2, bool set_it) -{ - abyte test_mask = abyte(1 << pos_in2.c_offset); - if (set_it) SET(_implementation->use(pos_in2.c_byte), test_mask); - else CLEAR((abyte &)_implementation->get(pos_in2.c_byte), test_mask); -} - -bool bit_vector::operator [](int position) const -{ - bounds_return(position, 0, _number_of_bits - 1, false); - return get_bit(into_two_dim(position)); -} - -void bit_vector::light(int position) -{ - bounds_return(position, 0, _number_of_bits - 1, ); - set_bit(into_two_dim(position), true); -} - -void bit_vector::clear(int position) -{ - bounds_return(position, 0, _number_of_bits - 1, ); - set_bit(into_two_dim(position), false); -} - -int bit_vector::find_first(bool to_find) const -{ - const abyte whole_set = abyte(0xFF); - // search through the whole bytes first. - for (int full_byte = 0; full_byte < _implementation->length(); full_byte++) { - if ( (to_find && _implementation->get(full_byte)) - || (!to_find && (_implementation->get(full_byte) != whole_set)) ) { - // the first appropriate byte is searched for the first appropriate bit. - for (int i = full_byte * BITS_PER_BYTE; i < minimum - (int(_number_of_bits), (full_byte+1)*BITS_PER_BYTE); i++) { - if (on(i) == to_find) return i; - } - return common::NOT_FOUND; - } - } - return common::NOT_FOUND; -} - -bool bit_vector::compare(const bit_vector &that, int start, int stop) const -{ - for (int i = start; i <= stop; i++) { - if (on(i) != that.on(i)) return false; - } - return true; -} - -astring bit_vector::text_form() const -{ - astring to_return; - int bits_on_line = 0; - const int estimated_elements_on_line = 64; - for (int i = 0; i < _number_of_bits; i++) { - // below prints out the bit number heading. - if (bits_on_line == 0) { - if (i != 0) to_return += "\n"; - if (i < 10000) to_return += " "; - if (i < 1000) to_return += " "; - if (i < 100) to_return += " "; - if (i < 10) to_return += " "; - to_return += a_sprintf("%d", i); - to_return += " | "; - } - if (on(i)) to_return += "1"; - else to_return += "0"; - bits_on_line++; - if (bits_on_line >= estimated_elements_on_line) bits_on_line = 0; - else if ( !(bits_on_line % BITS_PER_BYTE) ) to_return += " "; - } - to_return += "\n"; - return to_return; -} - -bit_vector bit_vector::subvector(int start, int end) const -{ - bounds_return(start, 0, bits() - 1, bit_vector()); - bounds_return(end, 0, bits() - 1, bit_vector()); - int size = end - start + 1; - bit_vector to_return(size); - for (int i = start; i <= end; i++) to_return.set_bit(i - start, on(i)); - return to_return; -} - -bool bit_vector::overwrite(int start, const bit_vector &to_write) -{ - bounds_return(start, 0, bits() - 1, false); - int end = start + to_write.bits() - 1; - bounds_return(end, 0, bits() - 1, false); - for (int i = start; i <= end; i++) set_bit(i, to_write[i - start]); - return true; -} - -enum endian { LEFT_ENDIAN, RIGHT_ENDIAN }; -endian host_byte_order = LEFT_ENDIAN; // bytes within words. -endian host_bit_order = LEFT_ENDIAN; // bits within bytes. - -// probably the treatment for right endian in either case -// of bytes or bits is wrong. - -bool bit_vector::set(int start, int size, basis::un_int source) -{ - bounds_return(start, 0, bits() - 1, false); - int end = start + size - 1; - bounds_return(end, 0, bits() - 1, false); - bounds_return(size, 1, 32, false); - bit_vector from_int(32, (abyte *)&source); - -// is this algorithm even remotely near the truth? - if (host_bit_order == RIGHT_ENDIAN) - from_int._implementation->resize(size, byte_array::NEW_AT_BEGINNING); - else from_int.resize(size); // left endian machine. - overwrite(start, from_int); - return true; -} - -basis::un_int bit_vector::get(int start, int size) const -{ - int end = start + size - 1; - bit_vector segment = subvector(start, end); - // padding to bytes. - int new_length = segment.bits(); - if (new_length % 8) { - new_length = ( (new_length+8) / 8) * 8; - LOG(a_sprintf("new size is %d.", new_length)); - } - segment.resize(new_length); - - if (host_bit_order == RIGHT_ENDIAN) { - bit_vector new_segment(segment.bits()); - for (int i = 0; i < segment.bits(); i += 8) - for (int j = i; j < i + BITS_PER_BYTE; j++) - if (j < segment.bits()) - new_segment.set_bit(i + (7 - (j - i)), segment[j]); - segment = new_segment; // copy the new form. - } - - LOG("new seg after bit copy:"); - LOG(segment); - - basis::un_int to_return = 0; - - int bytes_used = number_of_packets(segment.bits(), int(BITS_PER_BYTE)); - // 4 = # of bytes in a int. - for (int i = minimum(4, bytes_used) - 1; i >= 0; i--) { -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp(8, &segment._implementation->get(i)); - LOG(a_sprintf("%d: src bits %s", i, tmp.text_form().s())); -#endif - -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp4(32, (abyte *)&to_return); - LOG(a_sprintf("%d: pre shift dest %s", i, tmp4.text_form().s())); -#endif - if (host_byte_order == LEFT_ENDIAN) to_return <<= 8; - else to_return >>= 8; -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp5(32, (abyte *)&to_return); - LOG(a_sprintf("%d: post shift dest %s", i, tmp5.text_form().s())); -#endif - - basis::un_int mask = segment._implementation->get(i); - if (host_byte_order == RIGHT_ENDIAN) mask <<= 23; -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp3(32, (abyte *)&to_return); - LOG(a_sprintf("%d: pre dest bits %s", i, tmp3.text_form().s())); -#endif - SET(to_return, mask); -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp2(32, (abyte *)&to_return); - LOG(a_sprintf("%d: post dest bits %s", i, tmp2.text_form().s())); -#endif - } - -#ifdef DEBUG_BIT_VECTOR - bit_vector tmp(32, (abyte *)&to_return); - LOG(a_sprintf("final bits %s", tmp.text_form().s())); -#endif - return to_return; -} - -} //namespace. diff --git a/core/library/structures/bit_vector.h b/core/library/structures/bit_vector.h deleted file mode 100644 index 55abae95..00000000 --- a/core/library/structures/bit_vector.h +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef BIT_VECTOR_CLASS -#define BIT_VECTOR_CLASS - -/*****************************************************************************\ -* * -* Name : bit_vector * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace structures { - -//! An array of bits with operations for manipulating and querying individual bits. - -class bit_vector -{ -public: - bit_vector(); - //!< creates a zero length bit_vector. - - bit_vector(int size, const basis::abyte *initial = NIL); - //!< creates a bit_vector able to store "size" bits. - /*!< if initial is NIL, the vector is initialized to zero. otherwise, the - bits are copied from "initial". "initial" must be large enough for the - copying to succeed. */ - - bit_vector(const bit_vector &to_copy); - - ~bit_vector(); - - bit_vector &operator =(const bit_vector &to_copy); - - int bits() const; - //!< returns the number of bits in the vector. - - bool operator [] (int position) const; - //!< returns the value of the bit at the position specified. - - bool on(int position) const; - //!< returns true if the bit at "position" is set. - - bool off(int position) const; - //!< returns true if the bit at "position" is clear. - - void set_bit(int position, bool value); - //!< sets the bit at "position" to a particular "value". - - bool whole() const; - //!< returns true if entire vector is set. - - bool empty() const; - //!< returns true if entire vector is unset. - - int find_first(bool to_find) const; - //!< Seeks the first occurrence of "to_find". - /*!< locates the position at which a bit equal to to_find is located or it - returns common::NOT_FOUND if no bit of that value is in the vector. */ - - void light(int position); - //!< sets the value of the bit at "position". - - void clear(int position); - //!< clears the value of the bit at "position". - - void resize(int size); - //!< Changes the size of the bit_vector to "size" bits. - /*!< This keeps any bits that still fit. Any new bits are set to zero. */ - - void reset(int size); - //!< resizes the bit_vector and clears all bits in it. - - bool compare(const bit_vector &that, int start, int stop) const; - //!< true if "this" is the same as "that" between "start" and "stop". - - bool operator == (const bit_vector &that) const; - //!< returns true if "this" is equal to "that". - /*!< neither vector is changed. if the vectors do not have the same size, - false is returned. */ - - basis::astring text_form() const; - //!< prints a nicely formatted list of bits to a string. - - bit_vector subvector(int start, int end) const; - //!< Extracts a chunk of the vector between "start" and "end". - /*!< Returns a bit_vector that is copied from this one starting at "start" - and running until "end". An empty bit vector is returned if the indices - are out of range. */ - - bool overwrite(int start, const bit_vector &to_write); - //!< Stores bits from "to_write" into "this" at "start". - /*!< overwrites the contents of this bit_vector with the contents of - "to_write", beginning at "start" in this bit_vector. true is returned - for a successful write. false will be returned if the "start" is out of - range or if "to_write" is too large. */ - - basis::un_int get(int start, int size) const; - //!< gets a portion of the vector as an unsigned integer. - /*!< returns an integer (as interpreted by the operating system) where the - pattern of bits in that integer is identical to the bits in this - bit_vector, beginning at "start" and including enough bits for an - integer of "size" bits. */ - - bool set(int start, int size, basis::un_int source); - //!< puts the "source" value into the vector at "start". - /*!< sets the pattern of bits in this bit_vector beginning at "start" - identically to how they are stored in the integer "source", where the - integer is expected to be using "size" of its bits. the bits are copied - starting from the low end of "source", where the operating system - defines what the low end is. true is returned for a successful copying. */ - - operator const basis::byte_array &() const; - //!< returns a copy of the low-level implementation of the bit vector. - /*!< the first bit is stored at the bit in first byte, and so forth. */ - -private: - basis::byte_array *_implementation; //!< holds the real state of the bits. - int _number_of_bits; //!< the total number of bits possible in this vector. - - struct two_dim_location { int c_byte; int c_offset; }; - //!< a two-dimensional position given by byte number and offset within byte. - - two_dim_location into_two_dim(int position) const; - /*!< turns a bit position in the vector into a two dimensional position - of the byte number and a bit offset within that byte. */ - bool get_bit(const two_dim_location &pos_in2) const; - //!< returns the value of the bit given its two dimensional location. - void set_bit(const two_dim_location &pos_in2, bool value); - //!< sets the value of the bit if "value", and clears it if not. -}; - -////////////// - -// NOTE: these are operations on numbers, NOT on bit_vectors. - -//! returns a number based on "to_modify" but with "bits" turned on. -template -void SET(type &to_modify, type bits) { to_modify |= bits; } - -//! returns a number based on "to_modify" but with "bits" turned off. -template -void CLEAR(type &to_modify, type bits) { to_modify &= (type)~bits; } - -//! returns non-zero if the "bits" bit pattern is turned on in "to_test". -template -bool TEST(type to_test, type bits) { return bool(!(!(to_test & bits))); } - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/structures/byte_hasher.h b/core/library/structures/byte_hasher.h deleted file mode 100644 index f7631259..00000000 --- a/core/library/structures/byte_hasher.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef ROTATING_BYTE_HASHER_CLASS -#define ROTATING_BYTE_HASHER_CLASS - -/*****************************************************************************\ -* * -* Name : rotating_byte_hasher * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_hasher.h" -#include "checksums.h" -#include "hash_table.h" - -#include - -namespace structures { - -//! Implements a hashing algorithm based on the contents stored in an object. -/*! - This will only be usable for key types that have flat members; keys with - pointers limit the meaning of the hash value, or destroy the meaning if the - pointer value can change between lookups. Note that objects based on RTTI - will probably never work with this either since the compiler stores extra - data as part of the binary form for those objects. -*/ - -class rotating_byte_hasher : public virtual hashing_algorithm -{ -public: - virtual ~rotating_byte_hasher() {} - - virtual basis::un_int hash(const void *key_data, int key_length) const - { return checksums::hash_bytes(key_data, key_length); } - //!< returns a value that can be used for indexing into a hash table. - /*!< the returned value is loosely based on the "key_data" and the - "key_length" we are provided with. note: do not use a huge key length - for this or your hash table will be very slow; the key should probably - be limited to 16 or less. */ - - virtual hashing_algorithm *clone() const - { return new rotating_byte_hasher; } - //!< implements cloning of the algorithm object. -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/checksums.cpp b/core/library/structures/checksums.cpp deleted file mode 100644 index 16be2f1f..00000000 --- a/core/library/structures/checksums.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/*****************************************************************************\ -* * -* Name : checksums group * -* Authors: Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "checksums.h" - -#include - -using namespace basis; - -namespace structures { - -const int HIGHEST_MOD_VALUE = 32014; - -unsigned int checksums::bizarre_checksum(const abyte *data, int length) -{ - int sum = 0; - for (int i = 0; i < length; i++) sum += data[i] % 23 + i % 9; - sum = (sum % (HIGHEST_MOD_VALUE - 1)) + 1; - return (unsigned int)sum; -} - -// fletcher checksum is from Dr. Dobbs Journal May 1992, pp. 32-38. - -basis::un_short checksums::fletcher_checksum(const abyte *data, int length) -{ - int sum1 = 0; - basis::un_int sum2 = 0; - const abyte *buffer = data; - - while (length--) { - sum1 += int(*buffer++); - // check for overflow into high byte. - if (sum1 > 255) sum1++; - sum1 &= 0xFF; // remove any bits in high byte for next sum. - sum2 += basis::un_int(sum1); - } - if (sum1 == 255) sum1 = 0; - unsigned int fletch = basis::un_int(sum2 & 0xFF); - fletch <<= 8; - fletch |= basis::un_int(sum1 & 0xFF); - - return basis::un_short(fletch); -} - -basis::un_short checksums::rolling_fletcher_checksum(basis::un_short previous, const abyte *data, - int len) -{ return previous ^ fletcher_checksum(data, len); } - -abyte checksums::byte_checksum(const abyte *data, int length) -{ - abyte to_return = 0; - for (int i = 0; i < length; i++) to_return += abyte(data[i]); - return to_return; -} - -basis::un_int checksums::short_checksum(const abyte *data, int length) -{ - basis::un_int to_return = 0; - for (int i = 0; i < length; i++) to_return += data[i]; - return to_return; -} - -basis::un_int checksums::hash_bytes(const void *key_data, int key_length) -{ - if (!key_data) return 0; // error! - if (!key_length) return 0; // ditto! - - abyte *our_key = (abyte *)key_data; - abyte hashed[4] = { 0, 0, 0, 0 }; - - int fill_posn = 0; - for (int i = 0; i < key_length; i++) { - // add to the primary area. - hashed[fill_posn] = hashed[fill_posn] + our_key[i]; - fill_posn++; - if (fill_posn >= 4) fill_posn = 0; - // add to the secondary area (the next in rotation after primary). - hashed[fill_posn] = hashed[fill_posn] + (our_key[i] / 4); - } - - basis::un_int to_return = 0; - for (int j = 0; j < 4; j++) to_return = (to_return << 8) + hashed[j]; - return to_return; -} - -} //namespace. - diff --git a/core/library/structures/checksums.h b/core/library/structures/checksums.h deleted file mode 100644 index 4f3f0ba8..00000000 --- a/core/library/structures/checksums.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef CHECKSUMS_GROUP -#define CHECKSUMS_GROUP - -/*****************************************************************************\ -* * -* Name : checksums group * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -/* @file checksums.h - A collection of useful checksum calculators. -*/ - -namespace structures { - -class checksums -{ -public: - - static basis::abyte byte_checksum(const basis::abyte *data, int length); - //!< simple byte-sized checksum based on additive roll-over. - - static basis::un_int short_checksum(const basis::abyte *data, int length); - //!< simple shorty checksum based on additive roll-over. - - static basis::un_short fletcher_checksum(const basis::abyte *data, int length); - //!< A positionally computed error detection value. - - static basis::un_short rolling_fletcher_checksum(basis::un_short previous, const basis::abyte *data, int len); - //!< Fletcher checksums applied to streaming data. - /*!< this is not strictly a fletcher checksum, but it uses the normal - fletcher checksum on the specified data and XORs it with the "previous" - value of the checksum. this leads to a regenerable number that should - always be the same if done on the same data using the same chunking - factor (the "len"), although of course the last piece of data does not - have to be "len" bytes. */ - - static unsigned int bizarre_checksum(const basis::abyte *data, int length); - //!< A different type of checksum with somewhat unknown properties. - /*!< It attempts to be incorporate positioning of the bytes. */ - - static basis::un_int hash_bytes(const void *key_data, int key_length); - //!< returns a value that can be used for indexing into a hash table. - /*!< the returned value is loosely based on the "key_data" and the - "key_length" we are provided with. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/hash_table.h b/core/library/structures/hash_table.h deleted file mode 100644 index 4eb02856..00000000 --- a/core/library/structures/hash_table.h +++ /dev/null @@ -1,597 +0,0 @@ -#ifndef HASH_TABLE_CLASS -#define HASH_TABLE_CLASS - -/*****************************************************************************\ -* * -* Name : hash_table * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "amorph.h" - -#include -#include -#include -#include -#include - -#include - -namespace structures { - -// forward. -template class internal_hash_array; - -//! A hashing algorithm takes a key and derives a related integer from it. -/*! - The hashing_algorithm is used in hash_tables for placing objects into slots - that can be easily found again. The numerical hash should be faster than - actually doing a search on what might be unsorted data otherwise. -*/ - -class hashing_algorithm : public virtual basis::clonable -{ -public: - virtual basis::un_int hash(const void *key_data, int key_length) const = 0; - //!< returns a hash value based on the "key_data" and "key_length". - /*!< the "key_length" is provided from the sizeof() of the key type used - in the hash_table (below). it is up to the implementor to create a hash - value that spreads the keys to be hashed appropriately. if similar keys - create same or similar hash values, then the efficiency of the hash_table - is compromised. */ - - virtual hashing_algorithm *clone() const = 0; - //!< supports virtual construction of new algorithm objects. -}; - -////////////// - -//! Implements hashing into buckets for quick object access. -/*! - The buckets are currently simple lists, but if the hashing algorithm is well - chosen, then that's not a major problem. This makes lookups a lot faster - than a linear search, but no particular performance guarantees are made at - this time (hmmm). -*/ - -template - // the key_type must support valid copy constructor, assignment operator and - // equality operators. the contents need not support any special operations; - // it is always considered as just a pointer. -class hash_table : public virtual basis::nameable -{ -public: - hash_table(const hashing_algorithm &hasher, int estimated_elements); - //!< Creates a table using the "hasher" that is ready to store "estimated_elements". - /*!< the "hasher" must provide the hashing algorithm for the two types - specified. if the implementation is thread safe, then one object can - be constructed to use with all the same types of hash tables. - note that "hasher" must exist longer than any classes based on it; do - not let "hasher" go out of scope or the hash table will explode. */ - - virtual ~hash_table(); - //!< destroys any objects left in the hash_table. - - DEFINE_CLASS_NAME("hash_table"); - - void rehash(int estimated_elements); - //!< resizes the hashing structures to optimise for a new size of "estimated_elements". - /*!< this is a potentially expensive operation. */ - - enum outcomes { - IS_NEW = basis::common::IS_NEW, - EXISTING = basis::common::EXISTING - }; - - static int calculate_num_slots(int estimated_elements); - //!< a helper method that lets us know what n is for how many 2^n slots we should have. - - int elements() const; - //!< the number of valid items we found by traversing the hash table. - /*!< this is a very costly method. */ - - int estimated_elements() const { return c_estim_elements; } - //!< returns the size of table we're optimized for. - - basis::outcome add(const key_type &key, contents *to_store); - //!< Stores "to_store" into the table given its "key" for hashing. - /*!< This places an entry in the hash table with the contents "to_store", - using the "key" structure to identify it. the return value reports whether - the "key" was already in the list or not. if the "key" was already in use, - then the contents for it get replaced with "to_store". note that the hash - table assumes ownership of the "to_store" object but just makes a copy of - the key. thus, if an item is replaced, the previous contents are - destroyed. */ - - basis::outcome fast_dangerous_add(const key_type &key, contents *to_store); - //!< Like the add method above, but doesn't check for duplicates. - /*!< This should only ever be used when one has already guaranteed that - the table doesn't have a duplicate item for the "key" specified. */ - - bool find(const key_type &key, contents * &item_found) const; - //!< locates the item specified by the "key", if possible. - /*!< true is returned on success and the "item_found" is filled in. the - "item_found" is a pointer to the actual data held in the table, so do not - destroy or otherwise damage the "item_found". */ - - contents *find(const key_type &key) const - { contents *c = NIL; return find(key, c)? c : NIL; } - //!< simplified form of above find() method. - - contents *acquire(const key_type &key); - //!< retrieves the contents held for "key" out of the table. - /*!< afterwards, the contents pointer is the caller's responsibility; it - is no longer in the table and must be destroyed externally. if no item - was found for the "key", then NIL is returned. */ - - bool zap(const key_type &key); - //!< removes the entry with the "key" specified. - /*!< true is returned if the item was found and destroyed. */ - - void reset(); - //!< removes all entries in the table and returns it to a pristine state. - - typedef bool apply_function(const key_type &key, contents ¤t, - void *data_link); - //!< the "apply_function" is what a user of the "apply" method must supply. - /*!< the function will be called on every item in the table unless one of - the invocations returns false; this causes the apply process to stop. - the "data_link" provides a way for the function to refer back to an - parent class of some sort. */ - - void apply(apply_function *to_apply, void *data_link); - //!< Invokes the function "to_apply" on every entry in the table. - /*!< This calls the "to_apply" function on every item in the catalog - until the function returns false. The "data_link" pointer is available to - the applied method and can be used to communicate an object for use during - the iteration. NOTE: it is NOT safe to rearrange or manipulate the table - in any way from your "to_apply" function. */ - - basis::outcome add(key_type *key, contents *to_store, bool check_dupes = true); - //!< specialized add for a pre-existing pointer "key". - /*!< responsibility for the "key" is taken over by the hash table, as of - course it is for the "to_store" pointer. this just allows others to - generate new keys and hand them over to us when it might otherwise be - very costly to copy the key structure. if "check_dupes" is not true, - then that asserts that you have independently verified that there's no - need to check whether the key is already present. */ - - bool verify() const; - //!< returns true if the hash table is internally consistent. - /*!< this is mainly for debugging. */ - -private: - int c_estim_elements; //!< expected running size for the hash table. - hashing_algorithm *_hasher; //!< algorithm for getting hash value. - internal_hash_array *_table; //!< storage area. - int _last_iter; - //!< tracks where we left off iterating. we restart just after that spot. - - hash_table(const hash_table &to_copy); - //!< not allowed; use the copy_hash_table function below. - hash_table &operator =(const hash_table &to_copy); - //!< not allowed; use the copy_hash_table function below. - -public: - internal_hash_array &table_access() const; - //!< special accessor for the copy_hash_table method only. -}; - -////////////// - -//! Copies the entire hash table, given a contents type that supports copying. -/*! - Provides a copy operation on hash tables where the contents object supports - a copy constructor, which is not appropriate to require in general. The - hash_table held in "target" will be wiped out and replaced with items - equivalent to those in "source". */ - -template -void copy_hash_table(hash_table &target, - const hash_table &source); - -////////////// - -// implementations for longer methods below.... - -// this is a very special micro-managed class. it holds two pointers which -// it neither creates nor destroys. thus all interaction with it must be -// careful about removing these objects at the appropriate time. we don't -// want automatic memory management since we want a cheap copy on the items -// in the buckets. - -template -class hash_wrapper : public virtual basis::root_object -{ -public: - key_type *_id; - contents *_data; - - hash_wrapper(key_type *id = NIL, contents *data = NIL) - : _id(id), _data(data) {} -}; - -////////////// - -template -class bucket - : public basis::array >, - public virtual basis::root_object -{ -public: - bucket() : basis::array >(0, NIL, - basis::byte_array::SIMPLE_COPY | basis::byte_array::EXPONE - | basis::byte_array::FLUSH_INVISIBLE) {} - - int find(const key_type &to_find) { - for (int i = 0; i < this->length(); i++) { - key_type *curr_key = this->get(i)._id; -//hmmm: if curr key is not set, is that a logic error? it seems like we -// are seeing the potential for this. - if (curr_key && (to_find == *curr_key)) - return i; - } - return basis::common::NOT_FOUND; - } -}; - -////////////// - -template -class internal_hash_array : public amorph > -{ -public: - internal_hash_array(int estimated_elements) - : amorph > - (hash_table::calculate_num_slots(estimated_elements)) {} -}; - -////////////// - -template -hash_table::hash_table(const hashing_algorithm &hasher, int estimated_elements) -: c_estim_elements(estimated_elements), - _hasher(hasher.clone()), - _table(new internal_hash_array(estimated_elements)), - _last_iter(0) -{} - -template -hash_table::~hash_table() -{ -#ifdef EXTREME_CHECKING - FUNCDEF("destructor"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - reset(); - basis::WHACK(_table); - basis::WHACK(_hasher); -} - -template -int hash_table::calculate_num_slots(int estimated_elements) -{ -//printf("elems wanted = %d\n", estimated_elements); - int log_2_truncated = int(log(float(estimated_elements)) / log(2.0)); -//printf("log 2 of elems, truncated = %d\n", log_2_truncated); - int slots_needed_for_elems = (int)pow(2.0, double(log_2_truncated + 1)); -//printf("slots needed = %d\n", slots_needed_for_elems ); - return slots_needed_for_elems; -} - -// the specialized copy operation. -template -void copy_hash_table(hash_table &target, - const hash_table &source) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("copy_hash_table"); - if (!source.verify()) - deadly_error(class_name(), func, "source state did not verify."); -#endif - target.reset(); - for (int i = 0; i < source.table_access().elements(); i++) { - bucket *buck = source.table_access().borrow(i); - if (!buck) continue; - for (int j = 0; j < buck->length(); j++) { - target.add(*((*buck)[j]._id), new contents(*((*buck)[j]._data))); - } - } -#ifdef EXTREME_CHECKING - if (!target.verify()) - deadly_error(class_name(), func, "target state did not verify."); -#endif - #undef class_name -} - -template -void hash_table::reset() -{ -#ifdef EXTREME_CHECKING - FUNCDEF("reset"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - // iterate over the list whacking the content items in the buckets. - for (int i = 0; i < _table->elements(); i++) { - bucket *buck = _table->acquire(i); - if (!buck) continue; - for (int j = 0; j < buck->length(); j++) { - basis::WHACK((*buck)[j]._data); // eliminate the stored data. - basis::WHACK((*buck)[j]._id); // eliminate the stored key. - buck->zap(j, j); // remove the element. - j--; // skip back before whack happened. - } - basis::WHACK(buck); - } -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif -} - -template -bool hash_table::verify() const -{ - for (int i = 0; i < _table->elements(); i++) { - const bucket *buck = _table->borrow(i); - if (!buck) continue; // that's acceptable. - for (int j = 0; j < buck->length(); j++) { - const hash_wrapper &wrap = (*buck)[j]; - if (!wrap._data) { -// program_wide_logger::get().log(a_sprintf("hash table: no data segment at position %d.", j)); - return false; - } - if (!wrap._id) { -// program_wide_logger::get().log(a_sprintf("hash table: no identifier at position %d.", j)); - return false; - } - } - } - return true; -} - -template -internal_hash_array &hash_table - ::table_access() const -{ return *_table; } - -template -void hash_table::rehash(int estimated_elements) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("rehash"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - typedef bucket buckie; - hash_table new_hash(*_hasher, estimated_elements); - // this is the new table that will be used. - - // scoot through the existing table so we can move items into the new one. - for (int i = 0; i < _table->elements(); i++) { - buckie *b = _table->borrow(i); // look at the current element. - if (!b) continue; // nothing here, keep going. - for (int j = b->length() - 1; j >= 0; j--) { - key_type *k = b->use(j)._id; - contents *c = b->use(j)._data; - new_hash.add(k, c); - } - b->reset(); - // clean out any items in the buckets here now that they've moved. - } - - // now flip the contents of the new guy into "this". - _table->reset(); // toss previous stuff. - _table->adjust(new_hash._table->elements()); - for (int q = 0; q < new_hash._table->elements(); q++) - _table->put(q, new_hash._table->acquire(q)); - // reset other data members. - c_estim_elements = new_hash.c_estim_elements; - _last_iter = 0; -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif -} - -template -basis::outcome hash_table::add(const key_type &key, - contents *to_store) -{ return add(new key_type(key), to_store); } - -template -basis::outcome hash_table::add(key_type *key, - contents *to_store, bool check_dupes) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("add"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - // get a hash value. - basis::un_int hashed = _hasher->hash((const void *)key, sizeof(key_type)); - // make the value appropriate for our table. - hashed = hashed % _table->elements(); - // see if the key already exists there. - bucket *buck = _table->borrow(hashed); - if (!buck) { - // this slot doesn't exist yet. - buck = new bucket; - _table->put(hashed, buck); - } - if (!check_dupes) { - // we aren't even going to check for its existence. - *buck += hash_wrapper(key, to_store); - return IS_NEW; - } - int indy = buck->find(*key); - if (basis::negative(indy)) { - // that value was not seen yet, so we're adding a new entry. - *buck += hash_wrapper(key, to_store); -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif - return IS_NEW; - } - // that key already existed, so we'll re-use its slot with the new data. - basis::WHACK((*buck)[indy]._data); - basis::WHACK(key); // toss since we're not using. - (*buck)[indy]._data = to_store; -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif - return EXISTING; -} - -template -basis::outcome hash_table::fast_dangerous_add - (const key_type &key, contents *to_store) -{ return add(new key_type(key), to_store, false); } - -template -bool hash_table::find(const key_type &key, - contents * &item_found) const -{ -#ifdef EXTREME_CHECKING - FUNCDEF("find"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - item_found = NIL; - // get a hash value. - basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); - // make the value appropriate for our table. - hashed = hashed % _table->elements(); - // see if the key exists in the bucket. - bucket *buck = _table->borrow(hashed); - if (!buck) return false; - int indy = buck->find(key); - if (basis::negative(indy)) return false; // not there. - // was there, so retrieve the data. - item_found = (*buck)[indy]._data; - return true; -} - -template -contents *hash_table::acquire(const key_type &key) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("acquire"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - // get a hash value. - basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); - // make the value appropriate for our table. - hashed = hashed % _table->elements(); - // see if the key exists in the bucket. - bucket *buck = _table->borrow(hashed); - if (!buck) return NIL; - int indy = buck->find(key); - if (basis::negative(indy)) return NIL; // nope, not there. - contents *to_return = (*buck)[indy]._data; - basis::WHACK((*buck)[indy]._id); // clean the id. - buck->zap(indy, indy); // toss the storage blob for the item. -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif - return to_return; -} - -template -bool hash_table::zap(const key_type &key) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("zap"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - // get a hash value. - basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); - // make the value appropriate for our table. - hashed = hashed % _table->elements(); - // see if the key exists in the bucket. - bucket *buck = _table->borrow(hashed); - if (!buck) return false; - int indy = buck->find(key); - if (basis::negative(indy)) { - // nope, not there. - return false; - } - basis::WHACK((*buck)[indy]._data); // delete the data held. - basis::WHACK((*buck)[indy]._id); // delete the data held. - buck->zap(indy, indy); // toss the storage blob for the item. - if (!buck->length()) { - // clean up this bucket now. - buck = _table->acquire(hashed); - basis::WHACK(buck); - } -#ifdef EXTREME_CHECKING - if (!verify()) - deadly_error(class_name(), func, "state did not verify afterwards."); -#endif - return true; -} - -template -void hash_table::apply(apply_function *to_apply, - void *data_link) -{ -#ifdef EXTREME_CHECKING - FUNCDEF("apply"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - typedef bucket buckie; - int bucks_seen = 0; - int posn = _last_iter; // start at the same place we left. - while (bucks_seen++ < _table->elements()) { - if ( (posn < 0) || (posn >= _table->elements()) ) - posn = 0; - buckie *b = _table->borrow(posn); - _last_iter = posn++; - // record where the iteration last touched and increment next position. - // we must do this before we check if the bucket exists or we'll rotate - // on this same place forever. - if (!b) continue; // nothing here, keep going. - for (int j = 0; j < b->length(); j++) { - contents *c = b->use(j)._data; - if (!c) { -#ifdef EXTREME_CHECKING - deadly_error("hash_table", "apply", "logic error--missing pointer"); -#endif - continue; - } - if (!to_apply(*b->use(j)._id, *c, data_link)) return; - } - } -} - -template -int hash_table::elements() const -{ -#ifdef EXTREME_CHECKING - FUNCDEF("elements"); - if (!verify()) deadly_error(class_name(), func, "state did not verify."); -#endif - typedef bucket buckie; - int to_return = 0; - for (int i = 0; i < _table->elements(); i++) { - bucket *buck = _table->borrow(i); - if (!buck) continue; // nothing to count. - to_return += buck->length(); - } - return to_return; -} - -#undef static_class_name - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/structures/int_hash.h b/core/library/structures/int_hash.h deleted file mode 100644 index bd5f3494..00000000 --- a/core/library/structures/int_hash.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef INT_HASH_CLASS -#define INT_HASH_CLASS - -/*****************************************************************************\ -* * -* Name : int_hash * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_hasher.h" -#include "hash_table.h" - -#include -#include - -namespace structures { - -//! A hash table for storing integers. -/*! - Implements a hash table indexed on integers that maintains a separate set of - identifiers for listing the items that are presently in the hash table. This - slows down additions somewhat, but finds are not affected. The advantage of - the separate index is that the apply() method is much faster. -*/ - -template -class int_hash : public hash_table -{ -public: - int_hash(int max_bits); - ~int_hash(); - - const int_set &ids() const; - void ids(int_set &ids) const; - //!< provides the current list of valid identifiers. - - basis::outcome add(int key, contents *to_store); - //!< overrides base add() and ensures that the id list stays up to date. - contents *acquire(int key); - //!< overrides base acquire() by ensuring that the ids stay up to date. - bool zap(int key); - //!< overrides base zap() method plus keeps id list updated. - void reset(); - //!< overrides base reset() and ensures that the id list stays up to date. - - typedef bool apply_function(const int &key, contents ¤t, - void *data_link); - - void apply(apply_function *to_apply, void *data_link); - //!< operates on every item in the int_hash table. - -private: - int_set *_ids; - //!< a separate list of the identifiers stored here. - /*! this provides a fairly quick way to iterate rather than having to span - the whole hash table. it does slow down zap() a bit though. */ -}; - -////////////// - -// implementations below... - -template -int_hash::int_hash(int max_bits) -: hash_table(rotating_byte_hasher(), max_bits), - _ids(new int_set) -{} - -template -int_hash::~int_hash() -{ WHACK(_ids); } - -template -const int_set &int_hash::ids() const { return *_ids; } - -template -void int_hash::ids(int_set &ids) const { ids = *_ids; } - -template -basis::outcome int_hash::add(int key, contents *to_store) -{ - _ids->add(key); - return hash_table::add(key, to_store); -} - -template -contents *int_hash::acquire(int key) -{ - _ids->remove(key); - return hash_table::acquire(key); -} - -template -bool int_hash::zap(int key) -{ - _ids->remove(key); - return hash_table::zap(key); -} - -template -void int_hash::reset() -{ - _ids->clear(); - hash_table::reset(); -} - -template -void int_hash::apply(apply_function *to_apply, void *data_link) -{ - for (int i = 0; i < _ids->elements(); i++) { - int current = (*_ids)[i]; - contents *found = hash_table::find(current); - if (!found) { - _ids->remove(current); - continue; - } - to_apply(current, *found, data_link); - } -} - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/structures/makefile b/core/library/structures/makefile deleted file mode 100644 index 78155565..00000000 --- a/core/library/structures/makefile +++ /dev/null @@ -1,10 +0,0 @@ -include cpp/variables.def - -PROJECT = structures -TYPE = library -SOURCE = bit_vector.cpp checksums.cpp memory_limiter.cpp object_packers.cpp \ - static_memory_gremlin.cpp string_hasher.cpp string_table.cpp version_record.cpp -TARGETS = structures.lib - -include cpp/rules.def - diff --git a/core/library/structures/matrix.h b/core/library/structures/matrix.h deleted file mode 100644 index 96ec3e07..00000000 --- a/core/library/structures/matrix.h +++ /dev/null @@ -1,352 +0,0 @@ -#ifndef MATRIX_CLASS -#define MATRIX_CLASS - -/*****************************************************************************\ -* * -* Name : matrix * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -namespace structures { - -//! Represents a two-dimensional array of objects. -/*! - The indices range from zero to (maximum_value - 1) for rows and columns. -*/ - -template -class matrix : protected basis::array -{ -public: - matrix(int rows = 0, int cols = 0, contents *data = NIL); - //!< the "data" array must have at least "rows" * "cols" contents in it. - matrix(const matrix &to_copy); - - ~matrix() {} - - int rows() const { return _rows; } - int columns() const { return _cols; } - - matrix &operator = (const matrix &to_copy); - - contents &get(int row, int column); - const contents &get(int row, int column) const; - //!< retrieves the contents from the specified "row" and "column". - - bool put(int row, int column, const contents &to_put); - //!< stores "to_put" into the matrix at "row" and "column". - - contents *operator[] (int row); - //!< dangerous: indexes by "row" into the underlying contents. - /*!< the result can be used as a pointer to the whole row of data, or it - can be indexed into by column to find a row/column position. this is - dangerous if one is not careful about ensuring that the column used is - within bounds. if the "row" parameter is out of bounds, then NIL is - returned. */ - const contents *operator[] (int row) const; - //!< dangerous: constant peek into data for "row" in underlying contents. - - matrix submatrix(int row, int column, int rows, int columns) const; - //!< returns a submatrix of this one starting at "row" and "column". - /*!< The returned matrix should contain "rows" rows and "columns" columns. - if the size or position are out of bounds, an empty matrix is returned. */ - void submatrix(matrix &sub, int row, int column, int rows, int cols) const; - //!< like submatrix() above, but stores into the "sub" parameter. - - matrix transpose() const; - //!< provides the transposed form of this matrix. - void transpose(matrix &resultant) const; - //!< stores the transpose of this matrix in "resultant". - - basis::array get_row(int row); - //!< return the vector at the "row", or an empty array if "row" is invalid. - basis::array get_column(int column); - //!< return the vector at the "column", or an empty array if it's invalid. - - bool insert_row(int before_row); - //!< inserts a row before the "before_" parameter. - /*!< all existing data is preserved in the matrix, although it may get - moved over depending on where it's located with respect to "before_row". */ - bool insert_column(int before_column); - //!< inserts a column before the "before_" parameter. - - void reset(int rows = 0, int columns = 0); - //!< empties the matrix and changes its size. - - void redimension(int new_rows, int new_columns); - //!< changes the size to contain "new_rows" by "new_columns" elements. - /*!< data that was held previously stays in the array as long as its row - and column indices are still valid. */ - - bool zap_row(int row_to_zap); - bool zap_column(int column_to_zap); - //!< removes a row or column from the matrix. - /*!< the matrix becomes one row or column less than before and all data - from the deleted vector is lost. */ - -private: - int _rows; //!< number of rows in the matrix. - int _cols; //!< number of columns in the matrix. - - int compute_index(int row, int column) const; - //!< returns the flat index that corresponds to the "row" and "column". -}; - -////////////// - -//! A matrix of integers. -class int_matrix : public matrix -{ -public: - int_matrix(int rows = 0, int cols = 0, int *data = NIL) - : matrix(rows, cols, data) {} - int_matrix(const matrix &to_copy) : matrix(to_copy) {} -}; - -//! A matrix of strings. -class string_matrix : public matrix -{ -public: - string_matrix(int rows = 0, int cols = 0, basis::astring *data = NIL) - : matrix(rows, cols, data) {} - string_matrix(const matrix &to_copy) : matrix(to_copy) {} -}; - -//! A matrix of double floating point numbers. -class double_matrix : public matrix -{ -public: - double_matrix(int rows = 0, int cols = 0, double *data = NIL) - : matrix(rows, cols, data) {} - double_matrix(const matrix &to_copy) : matrix(to_copy) {} -}; - -////////////// - -// implementation for longer methods... - -//hmmm: the operations for zapping use extra memory. they could easily -// be done as in-place copies. - -#undef static_class_name -#define static_class_name() "matrix" - // used in bounds_halt macro. - -template -matrix::matrix(int r, int c, contents *dat) -: basis::array(r*c, dat), _rows(r), _cols(c) {} - -template -matrix::matrix(const matrix &to_copy) -: basis::array(0), _rows(0), _cols(0) -{ *this = to_copy; } - -template -matrix &matrix::operator = (const matrix &to_copy) -{ - if (&to_copy == this) return *this; - basis::array::operator = (to_copy); - _rows = to_copy._rows; - _cols = to_copy._cols; - return *this; -} - -template -contents *matrix::operator[] (int r) -{ return &basis::array::operator [] (compute_index(r, 0)); } - -template -const contents *matrix::operator[] (int r) const -{ return &basis::array::operator [] (compute_index(r, 0)); } - -template -const contents &matrix::get(int r, int c) const -{ return basis::array::get(compute_index(r, c)); } - -template -contents &matrix::get(int row, int column) -{ return basis::array::operator [] (compute_index(row, column)); } - -template -int matrix::compute_index(int row, int column) const -{ return column + row * _cols; } - -template -void matrix::reset(int rows_in, int columns_in) -{ - if ( (_rows == rows_in) && (_cols == columns_in) ) { - // reuse space, but reset the objects to their initial state. - for (int i = 0; i < basis::array::length(); i++) - basis::array::operator [](i) = contents(); - return; - } - - _rows = 0; - _cols = 0; - basis::array::reset(0); - - this->insert(0, rows_in * columns_in); - _rows = rows_in; - _cols = columns_in; -} - -template -void matrix::redimension(int new_rows, int new_columns) -{ - if ( (_rows == new_rows) && (_cols == new_columns) ) return; - matrix new_this(new_rows, new_columns); - for (int r = 0; r < minimum(new_rows, rows()); r++) - for (int c = 0; c < minimum(new_columns, columns()); c++) - new_this[r][c] = (*this)[r][c]; - *this = new_this; -} - -template -bool matrix::put(int row, int column, const contents &to_put) -{ - if ( (row >= rows()) || (column >= columns()) ) - return false; - (operator [](row))[column] = to_put; - return true; -} - -template -matrix matrix::submatrix(int row, int column, int rows_in, - int columns_in) const -{ - matrix to_return; - submatrix(to_return, row, column, rows_in, columns_in); - return to_return; -} - -template -void matrix::submatrix(matrix &sub, int row, int column, - int rows_in, int columns_in) const -{ - sub.reset(); - if ( (row >= rows()) || (row + rows_in >= rows()) ) return; - if ( (column >= columns()) || (column + columns_in >= columns()) ) return; - sub.reset(rows_in, columns_in); - for (int r = row; r < row + rows_in; r++) - for (int c = column; c < column + columns_in; c++) - sub[r - row][c - column] = (*this)[r][c]; -} - -template -matrix matrix::transpose() const -{ - matrix to_return; - transpose(to_return); - return to_return; -} - -template -void matrix::transpose(matrix &resultant) const -{ - resultant.reset(columns(), rows()); - for (int i = 0; i < rows(); i++) - for (int j = 0; j < columns(); j++) - resultant[j][i] = (*this)[i][j]; -} - -template -basis::array matrix::get_row(int row) -{ - basis::array to_return; - if (row >= rows()) return to_return; - to_return.reset(columns()); - for (int i = 0; i < columns(); i++) - to_return[i] = get(row, i); - return to_return; -} - -template -basis::array matrix::get_column(int column) -{ - basis::array to_return; - if (column >= columns()) return to_return; - to_return.reset(rows()); - for (int i = 0; i < rows(); i++) - to_return[i] = get(i, column); - return to_return; -} - -template -bool matrix::zap_row(int row_to_zap) -{ - FUNCDEF("zap_row"); - bounds_halt(row_to_zap, 0, rows() - 1, false); - const int start = compute_index(row_to_zap, 0); - // this is only safe because the indices are stored in row-major order (which - // i hope means the right thing). in any case, the order is like so: - // 1 2 3 4 - // 5 6 7 8 - // thus we can whack a whole row contiguously. - basis::array::zap(start, start + columns() - 1); - _rows--; - return true; -} - -template -bool matrix::zap_column(int column_to_zap) -{ - FUNCDEF("zap_column"); - bounds_halt(column_to_zap, 0, columns() - 1, false); - // this starts at the end, which keeps the indexes meaningful. otherwise - // the destruction interferes with finding the elements. - for (int r = rows(); r >= 0; r--) { - const int loc = compute_index(r, column_to_zap); - basis::array::zap(loc, loc); - } - _cols--; - return true; -} - -template -bool matrix::insert_row(int position) -{ - FUNCDEF("insert_row"); - bounds_halt(position, 0, rows(), false); - // see comment in zap_row for reasoning about the below. - basis::array::insert(compute_index(position, 0), columns()); - _rows++; - // clear out those spaces. - for (int c = 0; c < columns(); c++) - put(position, c, contents()); - return true; -} - -template -bool matrix::insert_column(int position) -{ - FUNCDEF("insert_column"); - bounds_halt(position, 0, columns(), false); - // similarly to zap_column, we must iterate in reverse. - for (int r = rows(); r >= 0; r--) - basis::array::insert(compute_index(r, position), 1); - _cols++; - // clear out those spaces. - for (int r = 0; r < rows(); r++) - put(r, position, contents()); - return true; -} - -#undef static_class_name - -} //namespace. - -#endif - diff --git a/core/library/structures/memory_limiter.cpp b/core/library/structures/memory_limiter.cpp deleted file mode 100644 index b0756a8a..00000000 --- a/core/library/structures/memory_limiter.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/*****************************************************************************\ -* * -* Name : memory_limiter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "int_hash.h" -#include "memory_limiter.h" - -#include - -#include - -using namespace basis; - -namespace structures { - -#undef LOG -#define LOG(to_print) printf("%s\n", astring(to_print).s()) - -class ml_memory_record -{ -public: - int _usage; - - ml_memory_record(int initial) : _usage(initial) {} -}; - -////////////// - -class ml_memory_state_meter : public int_hash -{ -public: - ml_memory_state_meter() : int_hash(10) {} -}; - -////////////// - -memory_limiter::memory_limiter(int overall_limit, int individual_limit) -: _overall_limit(overall_limit), - _individual_limit(individual_limit), - _overall_size(0), - _individual_sizes(new ml_memory_state_meter) -{ -} - -memory_limiter::~memory_limiter() -{ - WHACK(_individual_sizes); -} - -void memory_limiter::reset() -{ - _overall_size = 0; - _individual_sizes->reset(); -} - -const int_set &memory_limiter::individuals_listed() const -{ return _individual_sizes->ids(); } - -ml_memory_record *memory_limiter::find_individual(int individual) const -{ - ml_memory_record *to_return = NIL; - if (!_individual_sizes->find(individual, to_return)) return NIL; - // no record for that guy. - return to_return; -} - -int memory_limiter::individual_usage(int individual) const -{ - ml_memory_record *found = find_individual(individual); - if (!found) return 0; - return found->_usage; -} - -int memory_limiter::individual_space_left(int individual) const -{ - if (!individual_limit()) return 0; - return individual_limit() - individual_usage(individual); -} - -astring memory_limiter::text_form(int indent) const -{ - astring to_return; - astring indentat(' ', indent); - - astring allowed = overall_limit()? - astring(astring::SPRINTF, "%dK", overall_limit() / KILOBYTE) - : "unlimited"; - astring avail = overall_limit()? - astring(astring::SPRINTF, "%dK", overall_space_left() / KILOBYTE) - : "unlimited"; - - to_return += astring(astring::SPRINTF, "Overall Limit=%s, Allocations=%dK, " - "Free Space=%s", allowed.s(), overall_usage() / KILOBYTE, avail.s()); - to_return += "\n"; - - int_set individuals = _individual_sizes->ids(); - for (int i = 0; i < individuals.elements(); i++) { - astring allowed = individual_limit()? - astring(astring::SPRINTF, "%dK", individual_limit() / KILOBYTE) - : "unlimited"; - astring avail = individual_limit()? - astring(astring::SPRINTF, "%dK", - individual_space_left(individuals[i]) / KILOBYTE) : "unlimited"; - - to_return += indentat + astring(astring::SPRINTF, "individual %d: " - "Limit=%s, Used=%dK, Free=%s", individuals[i], allowed.s(), - individual_usage(individuals[i]) / KILOBYTE, avail.s()); - to_return += "\n"; - } - if (!individuals.elements()) { - to_return += indentat + "No allocations owned currently."; - to_return += "\n"; - } - return to_return; -} - -bool memory_limiter::okay_allocation(int individual, int memory_desired) -{ -// FUNCDEF("okay_allocation"); - // check the overall allocation limits first. - if (_overall_limit - && (_overall_size + memory_desired > _overall_limit) ) return false; - // now check sanity of this request. - if (_individual_limit && (memory_desired > _individual_limit) ) return false; - // now check the allocations per user. - ml_memory_record *found = find_individual(individual); - if (!found) { - _individual_sizes->add(individual, new ml_memory_record(0)); - found = find_individual(individual); - if (!found) { - LOG("ERROR: adding a new record to the memory state!"); - return false; - } - } - if (_individual_limit - && (found->_usage + memory_desired > _individual_limit) ) - return false; - found->_usage += memory_desired; - _overall_size += memory_desired; - return true; -} - -bool memory_limiter::record_deletion(int individual, int memory_deleted) -{ - if (memory_deleted < 0) return false; // bogus. - // make sure the individual exists. - ml_memory_record *found = find_individual(individual); - if (!found) return false; - // the individual must have actually allocated at least that much previously. - if (found->_usage < memory_deleted) return false; - // okay, we think that's reasonable. - found->_usage -= memory_deleted; - _overall_size -= memory_deleted; - // clean out an empty locker. - if (!found->_usage) _individual_sizes->zap(individual); - return true; -} - -} //namespace. - - diff --git a/core/library/structures/memory_limiter.h b/core/library/structures/memory_limiter.h deleted file mode 100644 index 1cb0e9fc..00000000 --- a/core/library/structures/memory_limiter.h +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef MEMORY_LIMITER_CLASS -#define MEMORY_LIMITER_CLASS - -/*****************************************************************************\ -* * -* Name : memory_limiter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace structures { - -// forward. -class ml_memory_record; -class ml_memory_state_meter; - -//! Tracks memory currently in use by memory manager objects. -/*! - The manager is given the ability to control overall memory usage as well - as to track memory usage per each user of the memory (assuming that the - memory is granted to unique users indexable via an integer). -*/ - -class memory_limiter -{ -public: - memory_limiter(int overall_limit, int individual_limit); - //!< creates a limiter that allows "overall_limit" bytes to be in use. - /*!< any attempts to add new memory after that limit is reached will be - rejected. if "overall_limit" is zero, then no limit is enforced on the - amount of memory that can be used in total. the "individual_limit" - specifies per-user limits, where each user of memory is identified by a - unique integer and where all users are granted equal rights to allocate - memory. "individual_limit" can also be given as zero, meaning no limit - is enforced per individual. note that "overall_limit" should usually be - many multiples of the "individual_limit", as appropriate to how many users - are expected. */ - - virtual ~memory_limiter(); - - DEFINE_CLASS_NAME("memory_limiter"); - - int overall_limit() const { return _overall_limit; } - //!< returns the current overall limit. - int individual_limit() const { return _individual_limit; } - //!< returns the current individual limit. - - int overall_usage() const { return _overall_size; } - //!< returns the size used by all managed memory. - - int overall_space_left() const { return overall_limit() - overall_usage(); } - //!< returns the overall space left for allocation. - - int individual_usage(int individual) const; - //!< returns the amount of memory used by "individual". - - int individual_space_left(int individual) const; - //!< returns the space left for the individual specified. - - basis::astring text_form(int indent = 0) const; - //!< returns a string that lists out the overall plus individual limits. - /*!< "indent" is used for spacing the printed rows of information. */ - - bool okay_allocation(int individual, int memory_desired); - //!< returns true if "individual" may allocate "memory_desired" bytes. - /*!< false indicates that this memory must not be allocated if the limits - are to be adhered to, either because there is already too much used in - the system at large or because this user is already using their limit. */ - - bool record_deletion(int individual, int memory_deleted); - //!< acknowledges that the "individual" freed "memory_deleted" bytes. - /*!< returns true if the "individual" is known and if "memory_deleted" - could be subtracted from that object's usage count. failure of this method - indicates that this class is not being used properly; if memory was - okayed to be granted in okay_allocation, then the paired record_deletion - will always succeed (in any arbitrary order where the okay_allocation - succeeds and proceeds the matching record_deletion). if there are no - remaining allocations for this individual, then its record is removed. */ - - void reset(); - //!< returns the object to a pristine state. - - const structures::int_set &individuals_listed() const; - //!< reports the current set of individuals using memory. - /*!< to know whether one is using this class appropriately, check the - returned set. if one does not think there should be any memory in use, - then the set should be empty and overall_usage() should return zero. - if the overall_usage() is zero, but there are members in the set, then - there is an implementation error in memory_limiter. otherwise, if the - set is non-empty, then deleted memory has not been recorded. */ - -private: - int _overall_limit; //!< how many total bytes allowed? - int _individual_limit; //!< how many bytes may each user consume? - int _overall_size; //!< the current measured overall memory usage in bytes. - ml_memory_state_meter *_individual_sizes; //!< tracks memory per individual. - - ml_memory_record *find_individual(int individual) const; - //!< locates the record held for the "individual" specified or returns NIL. -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/object_packers.cpp b/core/library/structures/object_packers.cpp deleted file mode 100644 index 233ee8ad..00000000 --- a/core/library/structures/object_packers.cpp +++ /dev/null @@ -1,279 +0,0 @@ -/*****************************************************************************\ -* * -* Name : object_packers * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "object_packers.h" - -#include - -using namespace basis; - -namespace structures { - -// rotate_in and snag_out do most of the real "work", if any. - -void rotate_in(byte_array &attach_into, int to_attach, int size_in_bytes) -{ - basis::un_int temp = basis::un_int(to_attach); - for (int i = 0; i < size_in_bytes; i++) { - attach_into += abyte(temp % 0x100); - temp >>= 8; - } -} - -void snag_out(byte_array &eat_from, basis::un_int &accumulator, int size_in_bytes) -{ - accumulator = 0; - for (int i = 0; i < size_in_bytes; i++) { - accumulator <<= 8; - accumulator += eat_from[size_in_bytes - i - 1]; - } - eat_from.zap(0, size_in_bytes - 1); -} - -////////////// - -int packed_size(const byte_array &packed_form) -{ return 2 * sizeof(int) + packed_form.length(); } - -void attach(byte_array &packed_form, const byte_array &to_attach) -{ - obscure_attach(packed_form, to_attach.length()); - packed_form += to_attach; -} - -bool detach(byte_array &packed_form, byte_array &to_detach) -{ - un_int len = 0; - if (!obscure_detach(packed_form, len)) return false; - if (packed_form.length() < (int)len) return false; - to_detach = packed_form.subarray(0, len - 1); - packed_form.zap(0, len - 1); - return true; -} - -////////////// - -// these are the only "real" attach/detach functions on number types. the -// others are all faking it by calling these. - -void attach(byte_array &packed_form, basis::un_int to_attach) -{ rotate_in(packed_form, to_attach, 4); } - -bool detach(byte_array &packed_form, basis::un_int &to_detach) -{ - if (packed_form.length() < 4) return false; - basis::un_int temp; - snag_out(packed_form, temp, 4); - to_detach = basis::un_int(temp); - return true; -} - -void attach(byte_array &packed_form, basis::un_short to_attach) -{ rotate_in(packed_form, to_attach, 2); } - -bool detach(byte_array &packed_form, basis::un_short &to_detach) -{ - if (packed_form.length() < 2) return false; - basis::un_int temp; - snag_out(packed_form, temp, 2); - to_detach = basis::un_short(temp); - return true; -} - -void attach(byte_array &packed_form, abyte to_attach) -{ packed_form += to_attach; } - -bool detach(byte_array &packed_form, abyte &to_detach) -{ - if (packed_form.length() < 1) return false; - to_detach = packed_form[0]; - packed_form.zap(0, 0); - return true; -} - -////////////// - -void attach(byte_array &packed_form, int to_attach) -{ attach(packed_form, basis::un_int(to_attach)); } - -bool detach(byte_array &packed_form, int &to_detach) -{ return detach(packed_form, (basis::un_int &)to_detach); } - -//void attach(byte_array &packed_form, basis::un_long to_attach) -//{ attach(packed_form, basis::un_int(to_attach)); } - -//bool detach(byte_array &packed_form, basis::un_long &to_detach) -//{ return detach(packed_form, (basis::un_int &)to_detach); } - -//void attach(byte_array &packed_form, long to_attach) -//{ attach(packed_form, basis::un_int(to_attach)); } - -//bool detach(byte_array &packed_form, long &to_detach) -//{ return detach(packed_form, (basis::un_int &)to_detach); } - -void attach(byte_array &packed_form, short to_attach) -{ attach(packed_form, basis::un_short(to_attach)); } - -bool detach(byte_array &packed_form, short &to_detach) -{ return detach(packed_form, (basis::un_short &)to_detach); } - -void attach(byte_array &packed_form, char to_attach) -{ attach(packed_form, abyte(to_attach)); } - -bool detach(byte_array &packed_form, char &to_detach) -{ return detach(packed_form, (abyte &)to_detach); } - -void attach(byte_array &packed_form, bool to_attach) -{ attach(packed_form, abyte(to_attach)); } - -////////////// - -// can't assume that bool is same size as byte, although it should fit -// into a byte just fine. -bool detach(byte_array &packed_form, bool &to_detach) -{ - abyte chomp; - if (!detach(packed_form, chomp)) return false; - to_detach = !!chomp; - return true; -} - -// operates on a number less than 1.0 that we need to snag the next digit -// to the right of the decimal point from. -double break_off_digit(double &input) { -//printf(astring(astring::SPRINTF, "break input=%f\n", input).s()); - input *= 10.0; -//printf(astring(astring::SPRINTF, "after mult=%f\n", input).s()); - double mod_part = fmod(input, 1.0); -//printf(astring(astring::SPRINTF, "modded=%f\n", mod_part).s()); - double to_return = input - mod_part; -//printf(astring(astring::SPRINTF, "to ret=%f\n", to_return).s()); - input -= to_return; - return to_return; -} - -//hmmm: not very efficient! it's just packing and wasting bytes doing it... -int packed_size(double to_pack) -{ - byte_array packed; - attach(packed, to_pack); - return packed.length(); -} - -void attach(byte_array &packed_form, double to_pack) -{ - int exponent = 0; - double mantissa = frexp(to_pack, &exponent); - abyte pos = mantissa < 0.0? false : true; - mantissa = fabs(mantissa); -//printf("mant=%10.10f pos=%d expon=%d\n", mantissa, int(pos), exponent); - packed_form += pos; - attach(packed_form, exponent); - byte_array mantis; - // even if the double has 52 bits for mantissa (where ms docs say 44), - // a 16 digit bcd encoded number should handle the size (based on size of - // 2^52 in digits). - for (int i = 0; i < 9; i++) { - double dig1 = break_off_digit(mantissa); -//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig1)).s()); - double dig2 = break_off_digit(mantissa); -//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig2)).s()); - mantis += abyte(dig1 * 16 + dig2); - } - attach(packed_form, mantis); -//printf("attach exit\n"); -} - -bool detach(byte_array &packed_form, double &to_unpack) -{ -//printf("detach entry\n"); - if (packed_form.length() < 1) return false; // no sign byte. - abyte pos = packed_form[0]; -//printf(astring(astring::SPRINTF, "pos=%d\n", int(pos)).s()); - packed_form.zap(0, 0); - int exponent; - if (!detach(packed_form, exponent)) return false; -//printf(astring(astring::SPRINTF, "expon=%d\n", exponent).s()); - byte_array mantis; - if (!detach(packed_form, mantis)) return false; - double mantissa = 0; - for (int i = mantis.last(); i >= 0; i--) { - abyte chop = mantis[i]; - double dig1 = chop / 16; -//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig1)).s()); - double dig2 = chop % 16; -//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig2)).s()); - mantissa += dig2; - mantissa /= 10; - mantissa += dig1; - mantissa /= 10; - } -//printf(astring(astring::SPRINTF, "mant=%10.10f\n", mantissa).s()); - to_unpack = ldexp(mantissa, exponent); - if (!pos) to_unpack = -1.0 * to_unpack; -//printf("pos=%d\n", int(pos)); -//printf(astring(astring::SPRINTF, "to_unpack=%f\n", to_unpack).s()); -//printf("detach exit\n"); - return true; -} - -void attach(byte_array &packed_form, float to_pack) -{ attach(packed_form, double(to_pack)); } - -bool detach(byte_array &packed_form, float &to_unpack) -{ - double real_unpack; - bool to_return = detach(packed_form, real_unpack); - to_unpack = (float)real_unpack; - return to_return; -} - -////////////// - -void obscure_attach(byte_array &packed_form, un_int to_attach) -{ -//printf("initial value=%x\n", to_attach); - basis::un_int first_part = 0xfade0000; -//printf("first part curr=%x\n", first_part); - basis::un_int second_part = 0x0000ce0f; -//printf("second part curr=%x\n", second_part); - first_part = first_part | (to_attach & 0x0000ffff); -//printf("first part now=%x\n", first_part); - second_part = second_part | (to_attach & 0xffff0000); -//printf("second part now=%x\n", second_part); - attach(packed_form, first_part); - attach(packed_form, second_part); -} - -bool obscure_detach(byte_array &packed_form, un_int &to_detach) -{ - basis::un_int first_part; - basis::un_int second_part; - if (!detach(packed_form, first_part)) return false; - if (!detach(packed_form, second_part)) return false; -//printf("first part after unpack=%x\n", first_part); -//printf("second part after unpack=%x\n", second_part); - if (basis::un_int(first_part & 0xffff0000) != basis::un_int(0xfade0000)) return false; -//printf("first part with and=%x\n", first_part & 0xffff0000); - if (basis::un_int(second_part & 0x0000ffff) != basis::un_int(0x0000ce0f)) return false; -//printf("second part with and=%x\n", second_part & 0x0000ffff); - to_detach = int( (second_part & 0xffff0000) + (first_part & 0x0000ffff) ); -//printf("final result=%x\n", to_detach); - return true; -} - -////////////// - -} // namespace - diff --git a/core/library/structures/object_packers.h b/core/library/structures/object_packers.h deleted file mode 100644 index fecb7876..00000000 --- a/core/library/structures/object_packers.h +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef OBJECT_PACKERS_CLASS -#define OBJECT_PACKERS_CLASS - -/*****************************************************************************\ -* * -* Name : object_packers * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace structures { - -// the sizes in bytes of common objects. if the compiler doesn't match these, there will -// probably be severe tire damage. -const int PACKED_SIZE_BYTE = 1; -const int PACKED_SIZE_INT16 = 2; -const int PACKED_SIZE_INT32 = 4; - -// these functions pack and unpack popular data types. - -void attach(basis::byte_array &packed_form, bool to_attach); - //!< Packs a bool "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, bool &to_detach); - //!< Unpacks a bool "to_detach" from "packed_form". - -void attach(basis::byte_array &packed_form, basis::abyte to_attach); - //!< Packs a byte "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, basis::abyte &to_detach); - //!< Unpacks a byte "to_detach" from "packed_form". - -int packed_size(const basis::byte_array &packed_form); - //!< Reports the size required to pack a byte array into a byte array. -void attach(basis::byte_array &packed_form, const basis::byte_array &to_attach); - //!< Packs a byte_array "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, basis::byte_array &to_detach); - //!< Unpacks a byte_array "to_detach" from "packed_form". - -void attach(basis::byte_array &packed_form, char to_attach); - //!< Packs a char "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, char &to_detach); - //!< Unpacks a char "to_detach" from "packed_form". - -int packed_size(double to_pack); - //!< Reports how large the "to_pack" will be as a stream of bytes. -void attach(basis::byte_array &packed_form, double to_pack); - //!< Packs a double precision floating point "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, double &to_unpack); - //!< Unpacks a double precision floating point "to_attach" from "packed_form". - -void attach(basis::byte_array &packed_form, float to_pack); - //!< Packs a floating point "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, float &to_unpack); - //!< Unpacks a floating point "to_attach" from "packed_form". - -void attach(basis::byte_array &packed_form, int to_attach); - //!< Packs an integer "to_attach" into "packed_form". - /*!< This method and the other simple numerical storage methods use a little - endian ordering of the bytes. They are platform independent with respect to - endianness and will reassemble the number properly on any platform. */ -bool detach(basis::byte_array &packed_form, int &to_detach); - //!< Unpacks an integer "to_attach" from "packed_form". - -void obscure_attach(basis::byte_array &packed_form, basis::un_int to_attach); - //!< like the normal attach but shifts in some recognizable sentinel data. - /*!< this is slightly more sure than a simple integer attachment. it can - be used to make sure upcoming data is probably a valid int. */ -bool obscure_detach(basis::byte_array &packed_form, basis::un_int &to_detach); - //!< shifts the number back and checks validity, false returned if corrupted. - -/* -void attach(basis::byte_array &packed_form, long to_attach); - //!< Packs a long integer "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, long &to_detach); - //!< Unpacks a long integer "to_attach" from "packed_form". -*/ - -void attach(basis::byte_array &packed_form, short to_attach); - //!< Packs a short integer "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, short &to_detach); - //!< Unpacks a short integer "to_attach" from "packed_form". - -void attach(basis::byte_array &packed_form, basis::un_int to_attach); - //!< Packs an unsigned integer "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, basis::un_int &to_detach); - //!< Unpacks an unsigned integer "to_attach" from "packed_form". - -/* -void attach(basis::byte_array &packed_form, basis::un_long to_attach); - //!< Packs an unsigned long integer "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, basis::un_long &to_detach); - //!< Unpacks an unsigned long integer "to_attach" from "packed_form". -*/ - -void attach(basis::byte_array &packed_form, basis::un_short to_attach); - //!< Packs an unsigned short integer "to_attach" into "packed_form". -bool detach(basis::byte_array &packed_form, basis::un_short &to_detach); - //!< Unpacks an unsigned short integer "to_attach" from "packed_form". - -////////////// - -// helpful template functions for packing. - -//! provides a way to pack any array that stores packable objects. -template -void pack_array(basis::byte_array &packed_form, const basis::array &to_pack) { - obscure_attach(packed_form, to_pack.length()); - for (int i = 0; i < to_pack.length(); i++) to_pack[i].pack(packed_form); -} - -//! provides a way to unpack any array that stores packable objects. -template -bool unpack_array(basis::byte_array &packed_form, basis::array &to_unpack) { - to_unpack.reset(); - basis::un_int len; - if (!obscure_detach(packed_form, len)) return false; - basis::array swappy_array(len, NIL, to_unpack.flags()); - // we create an array of the specified length to see if it's tenable. - if (!swappy_array.observe()) return false; // failed to allocate. - for (int i = 0; i < (int)len; i++) { - if (!swappy_array[i].unpack(packed_form)) - return false; - } - // now that we've got exactly what we want, plunk it into the result array. - swappy_array.swap_contents(to_unpack); - return true; -} - -//! provides space estimation for the objects to be packed. -template -int packed_size_array(const basis::array &to_pack) { - int to_return = sizeof(int) * 2; // obscure version uses double int size. - for (int i = 0; i < to_pack.length(); i++) - to_return += to_pack[i].packed_size(); - return to_return; -} - -//! Packs flat objects into an array of bytes. -/*! Similar to pack above, but operates on arrays with simple -objects that do not support functional pack and unpack. */ -template -void pack_simple(basis::byte_array &packed_form, const basis::array &to_pack) { - obscure_attach(packed_form, to_pack.length()); - for (int i = 0; i < to_pack.length(); i++) - attach(packed_form, to_pack[i]); -} - -//! Unpacks flat objects from an array of bytes. -/*! Similar to unpack above, but operates on arrays with simple -objects that do not support functional pack and unpack. */ -template -bool unpack_simple(basis::byte_array &packed_form, basis::array &to_unpack) { - to_unpack.reset(); - basis::un_int len; - if (!obscure_detach(packed_form, len)) return false; - basis::array swappy_array(len, NIL, to_unpack.flags()); - if (!swappy_array.observe()) return false; // failed to allocate. - for (int i = 0; i < len; i++) { - if (!detach(packed_form, swappy_array[i])) - return false; - } - swappy_array.swap_contents(to_unpack); - return true; -} - -} // namespace - -#endif - diff --git a/core/library/structures/pointer_hash.h b/core/library/structures/pointer_hash.h deleted file mode 100644 index dce57a56..00000000 --- a/core/library/structures/pointer_hash.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef POINTER_HASH_CLASS -#define POINTER_HASH_CLASS - -/*****************************************************************************\ -* * -* Name : pointer_hash * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_hasher.h" -#include "hash_table.h" -#include "pointer_hash.h" -#include "set.h" - -// forward. -class pointer_set; - -namespace structures { - -//! A hash table for storing pointers. -/*! - Implements a hash table indexed on pointer values that maintains a separate - set to list the items that are presently in the hash table. This slows down - additions somewhat, but finds are not affected. The advantage of the - separate index is that the apply() method is much faster. -*/ - -template -class pointer_hash : public hash_table -{ -public: - pointer_hash(int estimated_elements); - ~pointer_hash(); - - const pointer_set &ids() const; - void ids(pointer_set &ids) const; - //!< provides the current list of valid identifiers. - - basis::outcome add(void *key, contents *to_store); - //!< overrides base add() and ensures that the id list stays up to date. - contents *acquire(void *key); - //!< overrides base acquire() by ensuring that the ids stay up to date. - bool zap(void *key); - //!< overrides base zap() method plus keeps id list updated. - void reset(); - //!< overrides base reset() and ensures that the id list stays up to date. - - typedef bool apply_function(const void * &key, contents ¤t, - void *data_link); - - void apply(apply_function *to_apply, void *data_link); - //!< operates on every item in the pointer_hash table. - -private: - pointer_set *_ids; - //!< a separate list of the identifiers stored here. - /*! this provides a fairly quick way to iterate rather than having to span - the whole hash table. it does slow down zap() a bit though. */ -}; - -////////////// - -// implementations for larger methods below... - -template -pointer_hash::pointer_hash(int estimated_elements) -: hash_table(rotating_byte_hasher(), estimated_elements), - _ids(new pointer_set) -{} - -template -pointer_hash::~pointer_hash() -{ WHACK(_ids); } - -template -const pointer_set &pointer_hash::ids() const { return *_ids; } - -template -void pointer_hash::ids(pointer_set &ids) const { ids = *_ids; } - -template -basis::outcome pointer_hash::add(void *key, contents *to_store) -{ - _ids->add(key); - return hash_table::add(key, to_store); -} - -template -contents *pointer_hash::acquire(void *key) -{ - _ids->remove(key); - return hash_table::acquire(key); -} - -template -bool pointer_hash::zap(void *key) -{ - _ids->remove(key); - return hash_table::zap(key); -} - -template -void pointer_hash::reset() -{ - _ids->clear(); - hash_table::reset(); -} - -template -void pointer_hash::apply(apply_function *to_apply, void *data_link) -{ - for (int i = 0; i < _ids->elements(); i++) { - void *current = (*_ids)[i]; - contents *found = hash_table::find(current); - if (!found) { - _ids->remove(current); - continue; - } - to_apply(current, *found, data_link); - } -} - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/structures/roller.h b/core/library/structures/roller.h deleted file mode 100644 index 9308e710..00000000 --- a/core/library/structures/roller.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef ROLLER_CLASS -#define ROLLER_CLASS - -/*****************************************************************************\ -* * -* Name : roller * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace structures { - -//! Maintains a pseudo-unique identifier number and issues a new one on demand. -/*! - The unique id is templated on the type of identifier, but the type used must - support: - 1) assigment to a value, - 2) a greater than or equal operator (>=), and - 3) the increment operator (++). - Zero is often treated as the invalid / empty / inactive identifier, but - roller does not prevent its use since some ranges might need to bridge - between negative and positive numbers. -*/ - -template -class roller -{ -public: - roller(contents start_of_range, contents end_of_range); - //!< constructs a roller between the start and end ranges. - /*!< this constructs a roller that runs from the value "start_of_range" - and will stay smaller than "end_of_range" (unless the initial parameters - are screwed up; this class doesn't validate the start and end of range). - when the roller hits the end of range, then its value is reset to the - "start_of_range" again. */ - - ~roller(); - - // these report the constructor parameters. - contents minimum() { return _start_of_range; } - //!< the smallest value that the roller can have. - contents maximum() { return _end_of_range; } - //!< the outer limit of the roller; it should never reach this. - - contents next_id(); - //!< returns a unique (per instance of this type) id. - - contents current() const; - //!< returns the current id to be used; be careful! - /*!< this value will be the next one returned, so only look at the current - id and don't use it unwisely. this function is useful if you want to - assign an id provisionally but might not want to complete the issuance of - it. */ - - void set_current(contents new_current); - //!< allows the current id to be manipulated. - /*!< this must be done with care lest existing ids be re-used. */ - -private: - contents _current_id; //!< the next id to bring forth. - contents _start_of_range; //!< first possible value. - contents _end_of_range; //!< one more than last possible value. -}; - -////////////// - -//! A roller that's based on integers. This is the most common type so far. - -class int_roller : public roller -{ -public: - int_roller(int start_of_range, int end_of_range) - : roller(start_of_range, end_of_range) {} -}; - -////////////// - -// implementations below... - -template -roller::roller(contents start, contents end) -: _current_id(start), _start_of_range(start), _end_of_range(end) {} - -template -void roller::set_current(contents new_current) -{ - _current_id = new_current; - if (_current_id >= _end_of_range) _current_id = _start_of_range; -} - -template roller::~roller() {} - -template contents roller::current() const -{ return _current_id; } - -template contents roller::next_id() -{ - contents to_return = _current_id; - if (to_return == _end_of_range) { - // somehow the id to return is at the end of the range. this probably - // means the end of range condition wasn't detected last time due to an - // error in the parameters or the operation of == or ++ in the templated - // class. - _current_id = _start_of_range; - to_return = _current_id; - } - _current_id++; // next id. - if (_current_id == _end_of_range) _current_id = _start_of_range; - // reset the current position when hits the end of the range. - return to_return; -} - -} //namespace. - -#endif - diff --git a/core/library/structures/set.h b/core/library/structures/set.h deleted file mode 100644 index 3215080f..00000000 --- a/core/library/structures/set.h +++ /dev/null @@ -1,315 +0,0 @@ -#ifndef SET_CLASS -#define SET_CLASS - -/*****************************************************************************\ -* * -* Name : set * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "object_packers.h" -#include "string_array.h" - -#include -#include -#include -#include - -namespace structures { - -//! Emulates a mathematical set, providing several standard set operations. -/*! - Note: this is not an efficient object and it should not be used for - sets of non-trivial sizes. -*/ - -template -class set : public basis::array -{ -public: - //! Constructs a set with "num" elements, copying them from "init". - /*! Be very careful to ensure that the array "init" has sufficient length - for "num" elements to be copied from it. */ - set(int num = 0, const contents *init = NIL, - basis::un_short flags = basis::array::EXPONE) - : basis::array(num, init, flags) {} - - ~set() {} //!< Destroys any storage held for the set. - - int elements() const { return this->length(); } - //!< Returns the number of elements in this set. - - bool empty() const { return elements() == 0; } - //!< Returns true if the set has no elements. - bool non_empty() const { return elements() != 0; } - //!< Returns true if the set has some elements. - - void clear() { this->reset(); } //!< Empties out this set. - - bool member(const contents &to_test) const; - //!< Returns true if the item "to_test" is a member of this set. - - bool add(const contents &to_add); - //!< Adds a new element "to_add" to the set. - /*!< This always succeeds, but will return true if the item was not - already present. */ - - set &operator += (const contents &to_add) - { add(to_add); return *this; } - //!< An algebraic operator synonym for add() that operates on the contents. - set &operator += (const set &to_add) - { unionize(to_add); return *this; } - //!< An algebraic operator synonym for add() that operates on a set. - - bool remove(const contents &to_remove); - //!< Removes the item "to_remove" from the set. - /*!< If it was not present, false is returned and the set is unchanged. */ - - set &operator -= (const contents &to_zap) - { remove(to_zap); return *this; } - //!< An algebraic operator synonym for remove that operates on the contents. - set &operator -= (const set &to_zap) - { differentiate(to_zap); return *this; } - //!< An algebraic operator synonym for remove that operates on a set. - - set set_union(const set &union_with) const; - //!< Implements the set union of "this" with "union_with". - /*!< This returns the set formed from the union of "this" set with the set - specified in "union_with". (unfortunately, the name "set_union" must - be used to distinguish from the C keyword "union".) */ - - void unionize(const set &union_with); - //!< Makes "this" set a union of "this" and "union_with". - - set operator + (const set &uw) const { return set_union(uw); } - //!< A synonym for set_union. - - set intersection(const set &intersect_with) const; - //!< Returns the intersection of "this" with the set in "intersect_with". - - set operator * (const set &iw) const { return intersection(iw); } - //!< A synonym for intersection. - - set difference(const set &differ_with) const; - //!< Returns the difference of this with "differ_with". - /*!< Difference is defined as the subset of elements in "this" that are not - also in "differ_with". */ - - void differentiate(const set &differ_with); - //!< Makes "this" set equal to the difference of "this" and "differ_with". - /*!< That is, after the call, "this" will only contain elements that were - not also in "differ_with". */ - - set operator - (const set &dw) const { return difference(dw); } - //!< A synonym for difference. - - int find(const contents &to_find) const; - //!< Returns the integer index of the item "to_find" in this set. - /*!< This returns a negative number if the index cannot be found. Note - that this only makes sense within our particular implementation of set as - an array. */ - - //! Zaps the entry at the specified "index". - /*! This also treats the set like an array. The index must be within the - bounds of the existing members. */ - bool remove_index(int index) - { return this->zap(index, index) == basis::common::OKAY; } -}; - -////////////// - -//! provides a way to pack any set that stores packable objects. -template -void pack(basis::byte_array &packed_form, const set &to_pack) -{ - obscure_attach(packed_form, to_pack.elements()); - for (int i = 0; i < to_pack.elements(); i++) to_pack[i].pack(packed_form); -} - -//! provides a way to unpack any set that stores packable objects. -template -bool unpack(basis::byte_array &packed_form, set &to_unpack) -{ - to_unpack.clear(); - basis::un_int len; - if (!obscure_detach(packed_form, len)) return false; - contents to_fill; - for (int i = 0; i < (int)len; i++) { - if (!to_fill.unpack(packed_form)) return false; - to_unpack.add(to_fill); - } - return true; -} - -////////////// - -//! A simple object that wraps a templated set of ints. -class int_set : public set, public virtual basis::root_object -{ -public: - int_set() {} - //!< Constructs an empty set of ints. - int_set(const set &to_copy) : set(to_copy) {} - //!< Constructs a copy of the "to_copy" array. - - DEFINE_CLASS_NAME("int_set"); -}; - -//! A simple object that wraps a templated set of strings. -class string_set : public set, public virtual basis::packable -{ -public: - string_set() {} - //!< Constructs an empty set of strings. - string_set(const set &to_copy) - : set(to_copy) {} - //!< Constructs a copy of the "to_copy" array. - string_set(const string_array &to_copy) { - for (int i = 0; i < to_copy.length(); i++) - add(to_copy[i]); - } - - DEFINE_CLASS_NAME("string_set"); - - bool operator == (const string_set &compare) const { - for (int i = 0; i < elements(); i++) - if (get(i) != compare.get(i)) return false; - return true; - } - - operator string_array() const { - string_array to_return; - for (int i = 0; i < length(); i++) - to_return += get(i); - return to_return; - } - - virtual void pack(basis::byte_array &packed_form) const - { structures::pack(packed_form, *this); } - virtual bool unpack(basis::byte_array &packed_form) - { return structures::unpack(packed_form, *this); } - virtual int packed_size() const { - int to_return = sizeof(int) * 2; // length packed in, using obscure. - for (int i = 0; i < length(); i++) - to_return += get(i).length() + 1; - return to_return; - } -}; - -//! A set of pointers that hides the platform's pointer size. -class pointer_set : public set -{ -public: - pointer_set() {} - //!< Constructs an empty set of void pointers. - pointer_set(const set &to_copy) : set(to_copy) - {} - //!< Constructs a copy of the "to_copy" array. -}; - -////////////// - -// implementation for longer functions is below... - -template -bool set::member(const contents &to_test) const -{ - for (int i = 0; i < elements(); i++) - if (to_test == this->get(i)) - return true; - return false; -} - -template -bool set::add(const contents &to_add) -{ - if (member(to_add)) return false; - concatenate(to_add); - return true; -} - -template -int set::find(const contents &to_find) const -{ - for (int i = 0; i < elements(); i++) - if (to_find == this->get(i)) - return i; - return basis::common::NOT_FOUND; -} - -template -bool set::remove(const contents &to_remove) -{ - for (int i = 0; i < elements(); i++) - if (to_remove == this->get(i)) { - this->zap(i, i); - return true; - } - return false; -} - -template -set set::intersection(const set &intersect_with) const -{ - set created(0, NIL, this->flags()); - const set *smaller = this; - const set *larger = &intersect_with; - if (elements() > intersect_with.elements()) { - // switch the smaller one into place. - smaller = &intersect_with; - larger = this; - } - for (int i = 0; i < smaller->length(); i++) - if (larger->member(smaller->get(i))) - created.concatenate(smaller->get(i)); - return created; -} - -template -set set::set_union(const set &union_with) const -{ - set created = *this; - for (int i = 0; i < union_with.elements(); i++) - created.add(union_with.get(i)); - return created; -} - -template -void set::unionize(const set &union_with) -{ - for (int i = 0; i < union_with.elements(); i++) - add(union_with.get(i)); -} - -template -set set::difference(const set &differ_with) const -{ - set created = *this; - for (int i = 0; i < differ_with.elements(); i++) { - if (created.member(differ_with[i])) - created.remove(differ_with[i]); - } - return created; -} - -template -void set::differentiate(const set &differ_with) -{ - for (int i = 0; i < differ_with.elements(); i++) { - if (member(differ_with[i])) - remove(differ_with[i]); - } -} - -} // namespace. - -#endif - diff --git a/core/library/structures/stack.h b/core/library/structures/stack.h deleted file mode 100644 index edf3c6e5..00000000 --- a/core/library/structures/stack.h +++ /dev/null @@ -1,208 +0,0 @@ -#ifndef STACK_CLASS -#define STACK_CLASS - -/*****************************************************************************\ -* * -* Name : stack * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace structures { - -//! An abstraction that represents a stack data structure. -/*! - This behaves like a standard stack of objects, but it additionally allows - access to any item in the stack via an array style bracket operator. -*/ - -template -class stack -{ -public: - enum stack_kinds { BOUNDED, UNBOUNDED }; - - stack(int elements = 0); - //!< Creates a stack with room for the specified number of "elements". - /*!< If "elements" is zero, then the stack is an UNBOUNDED stack that - has no set limit on the number of elements it can contain (besides the - amount of memory available). on an unbounded stack, a result of IS_FULL - will never be returned--instead a memory allocation failure would occur. - If "elements" is greater than zero, then the stack is a BOUNDED stack - which can hold at maximum "elements" number of objects. for bounded - stacks, if there is to be an allocation failure, it will happen at the - time of stack construction, rather than during execution. */ - - stack(const stack &to_copy); - //!< constructs a stack as a copy of "to_copy". - - ~stack(); - //!< destroys anything left on the stack. - - void reset(); - //!< throws out all contents on the stack. - - stack_kinds kind() const { return _kind; } - //!< returns the type of stack that was constructed. - - basis::outcome push(const contents &element); - //!< Enters a new element onto the top of the stack. - /*!< if the stack is too large to add another element, then IS_FULL is - returned. if the element to push is nil, the stack is unchanged and - IS_EMPTY is returned. */ - - basis::outcome pop(); - //!< Removes the top element on the stack. - /*!< If the stack has no elements to be popped off, then IS_EMPTY is - returned. The element that was popped is destroyed. */ - - contents &top(); - //!< Returns the top element from the stack but doesn't change the stack. - /*!< This method does not pop the element! If the stack is empty, then a - bogus contents object is returned. */ - - basis::outcome acquire_pop(contents &to_stuff); - //!< Used to grab the top off of the stack. - /*!< this is basically a call to top() followed by a pop(). if there was - no top, then IS_EMPTY is returned. */ - - int size() const; - //!< returns the size of the stack. - /*!< if the stack is empty, then 0 is returned. */ - - stack &operator =(const stack &to_copy); - //!< makes this stack a copy of "to_copy". - - contents &operator [](int index); - //!< Accesses the item at position "index" in the stack. - /*!< Allows access to the stack in an impure fashion; elements other than - the top can be examined. Efforts to access elements that do not exist - are ignored. The range for the element numbers is as in C and runs - from 0 to size() - 1. */ - - void invert(); - //!< Inverts this stack, meaning that the old bottom is the new top. - - int elements() const; - //!< Returns the number of elements used by the stack. - /*!< For a bounded stack, this returns the number of elements the stack - was constructed to hold. For an unbounded stack, it returns the current - number of elements (which is the same as size()). Note though that it is - different from size() for a bounded size stack! */ - -private: - basis::array _store; //!< holds the contents of the stack. - stack_kinds _kind; //!< the type of stack we've got. - int _valid_fields; //!< count of the number of items actually in use here. -}; - -////////////// - -// implementations below... - -template -stack::stack(int elements) -: _store(elements >= 0? elements : 0), - _kind(_store.length()? BOUNDED : UNBOUNDED), - _valid_fields(0) -{} - -template -stack::stack(const stack &to_copy) -: _store(0), _valid_fields(0) -{ operator = (to_copy); } - -template stack::~stack() {} - -template -int stack::size() const { return _valid_fields; } - -template -void stack::reset() -{ - while (pop() == basis::common::OKAY) {} // pop off elements until all are gone. -} - -template -int stack::elements() const { return _store.length(); } - -template -basis::outcome stack::push(const contents &element) -{ - if (_kind == BOUNDED) { - if (_valid_fields >= elements()) return basis::common::IS_FULL; - basis::outcome result = _store.put(_valid_fields, element); - if (result != basis::common::OKAY) return basis::common::IS_FULL; - } else _store.concatenate(element); - _valid_fields++; - return basis::common::OKAY; -} - -template -basis::outcome stack::pop() -{ - if (_valid_fields < 1) return basis::common::IS_EMPTY; - if (_kind == UNBOUNDED) - _store.zap(_store.length() - 1, _store.length() - 1); - _valid_fields--; - return basis::common::OKAY; -} - -template -contents &stack::top() -{ return _store[_valid_fields - 1]; } - -template -stack &stack::operator = (const stack &to_copy) -{ - if (this == &to_copy) return *this; - reset(); - _kind = to_copy._kind; - _store.reset(to_copy._store.length()); - for (int i = 0; i < to_copy._store.length(); i++) - _store.put(i, to_copy._store[i]); - _valid_fields = to_copy._valid_fields; - return *this; -} - -template -void stack::invert() -{ - for (int i = 0; i < _store.length() / 2; i++) { - contents hold = _store.get(i); - int exchange_index = _store.length() - i - 1; - _store.put(i, _store.get(exchange_index)); - _store.put(exchange_index, hold); - } -} - -template -contents &stack::operator [](int index) -{ - if (index >= _valid_fields) index = -1; // force a bogus return. - return _store[index]; -} - -template -basis::outcome stack::acquire_pop(contents &to_stuff) -{ - if (!_valid_fields) return basis::common::IS_EMPTY; - to_stuff = _store[_valid_fields - 1]; - if (_kind == UNBOUNDED) _store.zap(elements()-1, elements()-1); - _valid_fields--; - return basis::common::OKAY; -} - -} //namespace. - -#endif - diff --git a/core/library/structures/static_memory_gremlin.cpp b/core/library/structures/static_memory_gremlin.cpp deleted file mode 100644 index b84bbb69..00000000 --- a/core/library/structures/static_memory_gremlin.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/*****************************************************************************\ -* * -* Name : static_memory_gremlin * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2004-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "static_memory_gremlin.h" - -#include - -#include -//temp! needed for fake continuable error etc - -#include -#include - -using namespace basis; - -namespace structures { - -//#define DEBUG_STATIC_MEMORY_GREMLIN - // comment this out to eliminate debugging print-outs. otherwise they - // are controlled by the class interface. - -//#define SKIP_STATIC_CLEANUP - // don't uncomment this unless you want all static objects to be left - // allocated on shutdown of the program. that's very sloppy but may - // sometimes be needed for testing. - -////////////// - -const int SMG_CHUNKING_FACTOR = 32; - // we'll allocate this many indices at a time. - -////////////// - -static bool __global_program_is_dying = false; - // this is set to true when no more logging or access to static objects - // should be allowed. - -////////////// - -class gremlin_object_record -{ -public: - root_object *c_object; - const char *c_name; -}; - -////////////// - -static_memory_gremlin::static_memory_gremlin() -: c_lock(), - c_top_index(0), - c_actual_size(0), - c_pointers(NIL), - c_show_debugging(false) -{ - ensure_space_exists(); -} - -static_memory_gremlin::~static_memory_gremlin() -{ - __global_program_is_dying = true; - // now the rest of the program is on notice; we're practically gone. - -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - if (c_show_debugging) - printf("SMG: beginning static object shutdown...\n"); -#endif - -#ifndef SKIP_STATIC_CLEANUP - // clean up any allocated pointers in reverse order of addition. - while (c_top_index > 0) { - // make sure we fixate on which guy is shutting down. some new ones - // could be added on the end of the list as a result of this destruction. - int zapped_index = c_top_index - 1; - gremlin_object_record *ptr = c_pointers[zapped_index]; - c_pointers[zapped_index] = NIL; - // since we know the one we're zapping, we no longer need that index. - c_top_index--; - // this should allow us to keep chewing on items that are newly being - // added during static shutdown, since we have taken the current object - // entirely out of the picture. - if (ptr) { -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - if (c_show_debugging) - printf((astring("SMG: deleting ") + ptr->c_object->instance_name() - + " called " + ptr->c_name - + a_sprintf(" at index %d.\n", zapped_index) ).s()); -#endif - WHACK(ptr->c_object); - WHACK(ptr); - } - } -#endif - delete [] c_pointers; - c_pointers = NIL; -} - -bool static_memory_gremlin::__program_is_dying() { return __global_program_is_dying; } - -mutex &static_memory_gremlin::__memory_gremlin_synchronizer() -{ - static mutex __globabl_synch_mem; - return __globabl_synch_mem; -} - -int static_memory_gremlin::locate(const char *unique_name) -{ - auto_synchronizer l(c_lock); - for (int i = 0; i < c_top_index; i++) { - if (!strcmp(c_pointers[i]->c_name, unique_name)) return i; - } - return common::NOT_FOUND; -} - -root_object *static_memory_gremlin::get(const char *unique_name) -{ - auto_synchronizer l(c_lock); - int indy = locate(unique_name); - if (negative(indy)) return NIL; - return c_pointers[indy]->c_object; -} - -const char *static_memory_gremlin::find(const root_object *ptr) -{ - auto_synchronizer l(c_lock); - for (int i = 0; i < c_top_index; i++) { - if (ptr == c_pointers[i]->c_object) - return c_pointers[i]->c_name; - } - return NIL; -} - -bool static_memory_gremlin::put(const char *unique_name, root_object *to_put) -{ - auto_synchronizer l(c_lock); - int indy = locate(unique_name); - // see if that name already exists. - if (non_negative(indy)) { -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - if (c_show_debugging) - printf((astring("SMG: cleaning out old object ") - + c_pointers[indy]->c_object->instance_name() - + " called " + c_pointers[indy]->c_name - + " in favor of object " + to_put->instance_name() - + " called " + unique_name - + a_sprintf(" at index %d.\n", indy)).s()); -#endif - WHACK(c_pointers[indy]->c_object); - c_pointers[indy]->c_object = to_put; - return true; - } -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - if (c_show_debugging) - printf((astring("SMG: storing ") + to_put->instance_name() - + " called " + unique_name - + a_sprintf(" at index %d.\n", c_top_index)).s()); -#endif - ensure_space_exists(); - c_pointers[c_top_index] = new gremlin_object_record; - c_pointers[c_top_index]->c_object = to_put; - c_pointers[c_top_index]->c_name = unique_name; - c_top_index++; - return true; -} - -void static_memory_gremlin::ensure_space_exists() -{ - auto_synchronizer l(c_lock); - if (!c_pointers || (c_top_index + 1 >= c_actual_size) ) { - // never had any contents yet or not enough space exists. -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - if (c_show_debugging) - printf(a_sprintf("SMG: adding space for top at %d.\n", c_top_index).s()); -#endif - c_actual_size += SMG_CHUNKING_FACTOR; - typedef gremlin_object_record *base_ptr; - gremlin_object_record **new_ptr = new base_ptr[c_actual_size]; - if (!new_ptr) { - throw "error: static_memory_gremlin::ensure_space_exists: failed to allocate memory for pointer list"; - } - for (int i = 0; i < c_actual_size; i++) new_ptr[i] = NIL; - for (int j = 0; j < c_actual_size - SMG_CHUNKING_FACTOR; j++) - new_ptr[j] = c_pointers[j]; - if (c_pointers) delete [] c_pointers; - c_pointers = new_ptr; - } -} - -// this function ensures that the space for the global objects is kept until -// the program goes away. if it's the first time through, then the gremlin -// gets created; otherwise the existing one is used. this function should -// always be called by the main program before any attempts to use global -// features like SAFE_STATIC or the program wide logger. it is crucial that no -// user-level threads have been created in the program before this is called. -static_memory_gremlin &static_memory_gremlin::__hoople_globals() -{ - static bool _initted = false; // tells whether we've gone through yet. - static static_memory_gremlin *_internal_gremlin = NIL; - // holds our list of shared pieces... - - if (!_initted) { -#ifdef DEBUG_STATIC_MEMORY_GREMLIN - printf("%s: initializing HOOPLE_GLOBALS now.\n", _global_argv[0]); -#endif - -#ifdef ENABLE_MEMORY_HOOK - void *temp = program_wide_memories().provide_memory(1, __FILE__, __LINE__); - // invoke now to get memory engine instantiated. - program_wide_memories().release_memory(temp); // clean up junk. -#endif - -#ifdef ENABLE_CALLSTACK_TRACKING - program_wide_stack_trace().full_trace_size(); - // invoke now to get callback tracking instantiated. -#endif - FUNCDEF("HOOPLE_GLOBALS remainder"); - // this definition must be postponed until after the objects that would - // track it actually exist. - if (func) {} // shut up the compiler about using it. - - // this simple approach is not going to succeed if the SAFE_STATIC macros - // are used in a static library which is then used in more than one dynamic - // library on win32. this is because each dll in win32 will have a - // different version of certain static objects that should only occur once - // per program. this problem is due to the win32 memory model, but in - // hoople at least this has been prevented; our only static library that - // appears in a bunch of dlls is basis and it is not allowed to use the - // SAFE_STATIC macro. - _internal_gremlin = new static_memory_gremlin; - _initted = true; - } - - return *_internal_gremlin; -} - -////////////// - -} // namespace. - diff --git a/core/library/structures/static_memory_gremlin.h b/core/library/structures/static_memory_gremlin.h deleted file mode 100644 index e53ee6d9..00000000 --- a/core/library/structures/static_memory_gremlin.h +++ /dev/null @@ -1,199 +0,0 @@ -#ifndef STATIC_MEMORY_GREMLIN_CLASS -#define STATIC_MEMORY_GREMLIN_CLASS - -/*****************************************************************************\ -* * -* Name : static_memory_gremlin * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace structures { - -// forward declarations. -class gremlin_object_record; - -//! Holds onto memory chunks that are allocated globally within the program. -/*! - The objects managed by the gremlin do not get destroyed until after the - program begins shutting down. This file also provides the SAFE_STATIC macros - that can be used for allocating static objects safely in a multi-threaded - program. -*/ - -class static_memory_gremlin : public virtual basis::nameable -{ -public: - static_memory_gremlin(); - - ~static_memory_gremlin(); - /*!< when destroyed, it is assumed that the program's lifetime is over and - all objects stored here are now unnecessary. this implements a - regenerative scheme for when static shutdowns occur out of order; if an - object has already been destroyed, it is recreated for the purposes of - other statics being shutdown. eventually this should stabilize since - it's completely unacceptable for static objects to depend on each other - in a cycle. */ - - DEFINE_CLASS_NAME("static_memory_gremlin"); - - static bool __program_is_dying(); - //!< Reports whether the program is shutting down currently. - /*!< If this flag is true, then code should generally just return without - doing anything where possible. It's especially important for long - running loops or active threads to stop if they see this, although - the normal program exit processes usually make it unnecessary. */ - - static static_memory_gremlin &__hoople_globals(); - //!< Holds onto objects that have been allocated in a program-wide scope. - /*!< These objects will have a lifetime up to the point of normal static - object destruction and then they will be cleaned up. */ - - static basis::mutex &__memory_gremlin_synchronizer(); - //!< private object for static_memory_gremlin's use only. - - void enable_debugging(bool verbose) { c_show_debugging = verbose; } - //!< if "verbose" is true, then the object will produce a noisy log. - - bool put(const char *unique_name, basis::root_object *ptr); - //!< adds a "ptr" to the set of static objects under the "unique_name". - /*!< the name must really be unique or objects will collide. we recommend - using an identifier based on a line number and filename where the static - is going to be placed (see the safe static implementation below). */ - - basis::root_object *get(const char *unique_name); - //!< locates the pointer held for the "unique_name", if any. - /*!< if no pointer exists, then NIL is returned. NOTE: the returned - pointer must not be destroyed, since the object could be used at any time - during the program's lifetime. */ - - const char *find(const basis::root_object *ptr); - //!< locates the name for "ptr" in our objects. - /*!< if it does not exist, then NIL is returned. */ - - void ensure_space_exists(); - /*!< makes sure that the list of objects is large enough to contain all of - the identifiers that have been issued. */ - -private: - basis::mutex c_lock; //!< protects object's state. - int c_top_index; //!< top place that's occupied in the list. - int c_actual_size; //!< the real number of indices in list. - gremlin_object_record **c_pointers; //!< storage for the static pointers. - bool c_show_debugging; //!< if true, then the object will log noisily. - - int locate(const char *unique_name); - //!< returns the index number of the "unique_name". -}; - -////////////// - -//! Statically defines a singleton object whose scope is the program's lifetime. -/*! - SAFE_STATIC is a macro that dynamically creates a function returning a particular - data type. the object is allocated statically, so that the same object will be - returned ever after until the program is shut down. the allocation is guarded so - that multiple threads cannot create conflicting static objects. - - note: in ms-win32, if you use this macro in a static library (rather than - a DLL), then the heap context is different. thus you could actually have - multiple copies of the underlying object. if the object is supposed to - be global and unique, then that's a problem. relocating the definitions - to a dll while keeping declarations in the static lib works (see the - program wide logger approach in ). - "type" is the object class to return. - "func_name" is the function to be created. - "parms" must be a list of parameters in parentheses or nothing. - - example usage: @code - SAFE_STATIC(connection_table, conntab, (parm1, parm2)) @endcode - will define a function: connection_table &conntab() - that returns a connection table object which has been created safely, - given that the main synchronizer was called from the main thread at least - once. @code - SAFE_STATIC(astring, static_string_doodle, ) @endcode - will define a static astring object named "static_string_doodle" that uses - the default constructor for astrings. -*/ -#define SAFE_STATIC(type, func_name, parms) \ - type &func_name() { SAFE_STATIC_IMPLEMENTATION(type, parms, __LINE__); } - -//! this version returns a constant object instead. -#define SAFE_STATIC_CONST(type, func_name, parms) \ - const type &func_name() \ - { SAFE_STATIC_IMPLEMENTATION(type, parms, __LINE__); } - -//! Statically defines a string for the rest of the program's life. -/*! This macro can be used to make functions returning a string a little -simpler. This can only be used when the string is constant. The usual way -to use this macro is in a function that returns a constant reference to a -string. The macro allocates the string to be returned statically so that all -future calls will refer to the stored string rather than recreating it again. */ -#define STATIC_STRING(str) \ - SAFE_STATIC_IMPLEMENTATION(astring, (str), __LINE__) - -////////////// - -//! this macro creates a unique const char pointer based on file location. -#define UNIQUE_NAME_BASED_ON_SOURCE(name, linenum) \ - static const char *name = "file:" __FILE__ ":line:" #linenum - -//! this blob is just a chunk of macro implementation for SAFE_STATIC... -/*! if the static object isn't initialized yet, we'll create it and store it -in the static_memory_gremlin. we make sure that the program isn't shutting -down, because that imposes a new requirement--previously created statics might -have already been destroyed. thus, during the program shutdown, we carefully -recreate any objects that were already toast. */ -#define SAFE_STATIC_IMPLEMENTATION(type, parms, linenum) \ -/*hmmm: bring all this back.*/ \ -/* const char *func = "allocation"; */ \ -/* frame_tracking_instance __trail_of_function("safe_static", func, \ - __FILE__, __LINE__, true); */ \ - UNIQUE_NAME_BASED_ON_SOURCE(__uid_name, linenum); \ -/* program_wide_memories(); */ \ - static basis::root_object *_hidden = NIL; \ - /* if haven't initialized yet, then definitely need to lock carefully. */ \ - if (structures::static_memory_gremlin::__program_is_dying() || !_hidden) { \ - basis::auto_synchronizer l(structures::static_memory_gremlin::__memory_gremlin_synchronizer()); \ - if (structures::static_memory_gremlin::__program_is_dying()) { \ - /* we can't rely on the pointer since we're shutting down currently. */ \ - _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ - } \ - if (!_hidden) { /* make sure no one scooped us. */ \ - /* try to retrieve an existing one first and use it if there. */ \ - _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ - if (!_hidden) { \ - _hidden = new type parms ; /* create the object finally. */ \ - /* store our object using the unique name for it. */ \ - if (!structures::static_memory_gremlin::__hoople_globals().put(__uid_name, _hidden)) { \ - /* we failed to allocate space. this is serious. */ \ - throw __uid_name; \ - } \ - } \ - } \ - } \ - if (!_hidden) { \ - /* grab the pointer that was stored, in case we're late getting here. */ \ - /* another thread might have scooped the object creation. */ \ - _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ - } \ - return *dynamic_cast(_hidden) - -// historical note: the SAFE_STATIC approach has existed since about 1998. -// however, the static_memory_gremlin's role in this started much later. - -} //namespace. - -#endif - diff --git a/core/library/structures/string_array.h b/core/library/structures/string_array.h deleted file mode 100644 index 22d5c3f4..00000000 --- a/core/library/structures/string_array.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef STRING_ARRAY_CLASS -#define STRING_ARRAY_CLASS - -/*****************************************************************************\ -* * -* Name : string_array * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "object_packers.h" - -#include -#include -#include - -namespace structures { - -//! An array of strings with some additional helpful methods. - -class string_array -: public basis::array, - public virtual basis::packable, - public virtual basis::equalizable -{ -public: - string_array(int number = 0, const basis::astring *initial_contents = NIL) - : basis::array(number, initial_contents, - EXPONE | FLUSH_INVISIBLE) {} - //!< Constructs an array of "number" strings. - /*!< creates a list of strings based on an initial "number" of entries and - some "initial_contents", which should be a regular C array of astrings - with at least as many entries as "number". */ - - //! a constructor that operates on an array of char pointers. - /*! be very careful with the array to ensure that the right number of - elements is provided. */ - string_array(int number, const char *initial_contents[]) - : basis::array(number, NIL, EXPONE | FLUSH_INVISIBLE) { - for (int i = 0; i < number; i++) { - put(i, basis::astring(initial_contents[i])); - } - } - - string_array(const basis::array &to_copy) - : basis::array(to_copy) {} - //!< copy constructor that takes a templated array of astring. - - DEFINE_CLASS_NAME("string_array"); - - //! Prints out a formatted view of the contained strings and returns it. - basis::astring text_format(const basis::astring &separator = ",", - const basis::astring &delimiter = "\"") const { - basis::astring to_return; - for (int i = 0; i < length(); i++) { - to_return += delimiter; - to_return += get(i); - to_return += delimiter; - if (i < last()) - to_return += separator; - } - return to_return; - } - - basis::astring text_form() const { return text_format(); } - //!< A synonym for the text_format() method. - -//hmmm: extract as re-usable equality operation. - - //! Compares this string array for equality with "to_compare". - bool equal_to(const equalizable &to_compare) const { - const string_array *cast = cast_or_throw(to_compare, *this); - if (length() != cast->length()) - return false; - for (int i = 0; i < length(); i++) - if (cast->get(i) != get(i)) - return false; - return true; - } - - //! locates string specified and returns its index, or negative if missing. - int find(const basis::astring &to_find) const { - for (int i = 0; i < length(); i++) { - if (to_find.equal_to(get(i))) return i; - } - return basis::common::NOT_FOUND; - } - - //! Returns true if all of the elements in this are the same in "second". - /*! The array "second" can have more elements, but must have all of the - items listed in this string array. */ - bool prefix_compare(const string_array &second) const { - if (second.length() < length()) return false; - if (!length()) return false; - for (int i = 0; i < length(); i++) - if ((*this)[i] != second[i]) return false; - return true; - } - - //! Packs this string array into the "packed_form" byte array. - virtual void pack(basis::byte_array &packed_form) const - { pack_array(packed_form, *this); } - - //! Unpacks a string array from the "packed_form" byte array. - virtual bool unpack(basis::byte_array &packed_form) - { return unpack_array(packed_form, *this); } - - //! Returns the number of bytes this string array would consume if packed. - virtual int packed_size() const { - int to_return = sizeof(int) * 2; // length packed in, using obscure. - for (int i = 0; i < length(); i++) - to_return += get(i).length() + 1; - return to_return; - } -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/string_hash.h b/core/library/structures/string_hash.h deleted file mode 100644 index 9a75ba9c..00000000 --- a/core/library/structures/string_hash.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef STRING_HASH_CLASS -#define STRING_HASH_CLASS - -/*****************************************************************************\ -* * -* Name : string_hash * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "hash_table.h" -#include "string_hasher.h" - -#include - -namespace structures { - -//! Implements a hash table indexed on character strings. - -template -class string_hash : public hash_table -{ -public: - string_hash(int estimated_elements) - : hash_table(astring_hasher(), estimated_elements) {} - - ~string_hash() {} -}; - -} //namespace. - -#endif // outer guard. - diff --git a/core/library/structures/string_hasher.cpp b/core/library/structures/string_hasher.cpp deleted file mode 100644 index 0762e794..00000000 --- a/core/library/structures/string_hasher.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/*****************************************************************************\ -* * -* Name : string_hasher * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "string_hasher.h" - -#include -#include - -using namespace basis; - -namespace structures { - -const int MAX_STRING_CHARS_USED = 3; - // we use this many characters from the string in question (if they - // exist) at each end of the string. - -////////////// - -hashing_algorithm *string_hasher::clone() const -{ return new string_hasher; } - -basis::un_int string_hasher::hash(const void *key_data, int key_length_in) const -{ - if (!key_data) return 0; // error! - if (key_length_in <= 1) return 0; // ditto! - - abyte *our_key = (abyte *)key_data; - abyte hashed[4] = { 0, 0, 0, 0 }; - - int key_length = minimum(key_length_in - 1, MAX_STRING_CHARS_USED); - - int fill_posn = 0; - // add the characters from the beginning of the string. - for (int i = 0; i < key_length; i++) { - // add to the primary area. - hashed[fill_posn] = hashed[fill_posn] + our_key[i]; - fill_posn++; - if (fill_posn >= 4) fill_posn = 0; - // add to the secondary area (the next in rotation after primary). - hashed[fill_posn] = hashed[fill_posn] + (our_key[i] / 4); - } - // add the characters from the end of the string. - for (int k = key_length_in - 2; - (k >= 0) && (k >= key_length_in - 1 - key_length); k--) { - // add to the primary area. - hashed[fill_posn] = hashed[fill_posn] + our_key[k]; - fill_posn++; - if (fill_posn >= 4) fill_posn = 0; - // add to the secondary area (the next in rotation after primary). - hashed[fill_posn] = hashed[fill_posn] + (our_key[k] / 4); - } - - basis::un_int to_return = 0; - for (int j = 0; j < 4; j++) to_return = (to_return << 8) + hashed[j]; - return to_return; -} - -////////////// - -hashing_algorithm *astring_hasher::clone() const -{ return new astring_hasher; } - -basis::un_int astring_hasher::hash(const void *key_data, int key_length_in) const -{ - if (!key_data) return 0; // error. - const astring *real_key = (const astring *)key_data; - if (real_key->length() + 1 != key_length_in) { -// printf("differing key lengths, string len=%d, key len=%d\n", -// real_key->length() + 1, key_length_in); - } - return string_hasher().hash((const void *)real_key->observe(), - real_key->length() + 1); -} - -} //namespace. - diff --git a/core/library/structures/string_hasher.h b/core/library/structures/string_hasher.h deleted file mode 100644 index 87e45e9b..00000000 --- a/core/library/structures/string_hasher.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef STRING_HASHER_CLASS -#define STRING_HASHER_CLASS - -/*****************************************************************************\ -* * -* Name : string_hasher * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "hash_table.h" - -namespace structures { - -//! Implements a simple hashing algorithm for strings. -/*! This uses a portion of the string's contents to create a hash value. */ - -class string_hasher : public virtual hashing_algorithm -{ -public: - virtual basis::un_int hash(const void *key_data, int key_length) const; - //!< returns a value that can be used to index into a hash table. - /*!< the returned value is loosely based on the "key_data" and the - "key_length" we are provided with. it is expected that the "key_data" - really is a 'char' pointer whose length is "key_length" (including the - zero terminator at the end). */ - - virtual hashing_algorithm *clone() const; - //!< implements cloning of the algorithm object. -}; - -////////////// - -class astring_hasher : public virtual hashing_algorithm -{ -public: - virtual basis::un_int hash(const void *key_data, int key_length) const; - //!< similar to string_hasher, but expects "key_data" as an astring pointer. - - virtual hashing_algorithm *clone() const; - //!< implements cloning of the algorithm object. -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/string_table.cpp b/core/library/structures/string_table.cpp deleted file mode 100644 index fb0b7f52..00000000 --- a/core/library/structures/string_table.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/*****************************************************************************\ -* * -* Name : string_table * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "object_packers.h" -#include "string_table.h" -#include "symbol_table.h" - -#include -#include - -using namespace basis; - -namespace structures { - -string_table::string_table(const string_table &to_copy) -: symbol_table(to_copy.estimated_elements()), - _add_spaces(false) -{ - *this = to_copy; -} - -string_table::~string_table() {} - -bool string_table::is_comment(const astring &to_check) -{ return to_check.begins(STRTAB_COMMENT_PREFIX); } - -string_table &string_table::operator = (const string_table &to_copy) -{ - if (this == &to_copy) return *this; - (symbol_table &)*this = (const symbol_table &)to_copy; - _add_spaces = to_copy._add_spaces; - return *this; -} - -astring string_table::text_form() const -{ - astring output; - const char *space_char = ""; - if (_add_spaces) space_char = " "; - for (int i = 0; i < symbols(); i++) { - if (is_comment(name(i))) - output += a_sprintf("%s\n", operator[](i).s()); - else - output += a_sprintf("%s%s=%s%s\n", name(i).s(), space_char, - space_char, operator[](i).s()); - } - return output; -} - -bool string_table::operator ==(const string_table &to_compare) const -{ - if (to_compare.symbols() != symbols()) return false; - for (int i = 0; i < symbols(); i++) { - const astring &key = name(i); - astring *str1 = find(key); - astring *str2 = to_compare.find(key); - if (!str2) return false; - if (*str1 != *str2) return false; - } - return true; -} - -int string_table::packed_size() const -{ - int size = sizeof(int); - for (int i = 0; i < symbols(); i++) { - size += name(i).length(); - size += operator[](i).length(); - } - return size; -} - -void string_table::pack(byte_array &packed_form) const -{ - structures::attach(packed_form, symbols()); - for (int i = 0; i < symbols(); i++) { - name(i).pack(packed_form); - operator[](i).pack(packed_form); - } -} - -bool string_table::unpack(byte_array &packed_form) -{ - reset(); - int syms; - if (!structures::detach(packed_form, syms)) return false; - for (int i = 0; i < syms; i++) { - astring name, content; - if (!name.unpack(packed_form)) return false; - if (!content.unpack(packed_form)) return false; - outcome ret = add(name, content); - if (ret != common::IS_NEW) return false; - } - return true; -} - -} //namespace. - - diff --git a/core/library/structures/string_table.h b/core/library/structures/string_table.h deleted file mode 100644 index 611c6258..00000000 --- a/core/library/structures/string_table.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef STRING_TABLE_CLASS -#define STRING_TABLE_CLASS - -/*****************************************************************************\ -* * -* Name : string_table * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "symbol_table.h" - -#include -#include - -namespace structures { - -//! Provides a symbol_table that holds strings as the content. -/*! This is essentially a table of named strings. */ - -class string_table -: public symbol_table, - public virtual basis::packable, - public virtual basis::hoople_standard -{ -public: - string_table(int estimated_elements = 100) : symbol_table(estimated_elements), - _add_spaces(false) {} - //!< the "estimated_elements" specifies how many items to prepare to efficiently hold. - string_table(const string_table &to_copy); - virtual ~string_table(); - - DEFINE_CLASS_NAME("string_table"); - - string_table &operator = (const string_table &to_copy); - - bool operator ==(const string_table &to_compare) const; - - virtual bool equal_to(const equalizable &to_compare) const { - const string_table *cast = dynamic_cast(&to_compare); - if (!cast) return false; - return operator ==(*cast); - } - - #define STRTAB_COMMENT_PREFIX "#comment#" - //!< anything beginning with this is considered a comment. - /*!< a numerical uniquifier should be appended to the string to ensure that - multiple comments can be handled per table. */ - - static bool is_comment(const basis::astring &to_check); - - basis::astring text_form() const; - //!< prints the contents of the table into the returned string. - /*!< if names in the table start with the comment prefix (see above), then - they will not be printed as "X=Y" but instead as just "Y". */ - - virtual void text_form(basis::base_string &fill) const { fill = text_form(); } - - // dictates whether the output will have spaces between the assignment - // character and the key name and value. default is to not add them. - bool add_spaces() const { return _add_spaces; } - void add_spaces(bool add_them) { _add_spaces = add_them; } - - virtual int packed_size() const; - virtual void pack(basis::byte_array &packed_form) const; - virtual bool unpack(basis::byte_array &packed_form); - -private: - bool _add_spaces; // records whether we add spaces around the assignment. -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/symbol_table.h b/core/library/structures/symbol_table.h deleted file mode 100644 index ee4bc246..00000000 --- a/core/library/structures/symbol_table.h +++ /dev/null @@ -1,467 +0,0 @@ -#ifndef SYMBOL_TABLE_CLASS -#define SYMBOL_TABLE_CLASS - -/*****************************************************************************\ -* * -* Name : symbol_table * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "pointer_hash.h" -#include "string_hash.h" -#include "symbol_table.h" - -#include -#include -#include - -namespace structures { - -template class internal_symbol_indexer; -template class internal_symbol_info; -template class internal_symbol_list; - -//! Maintains a list of names, where each name has a type and some contents. - -template -class symbol_table -{ -public: - //! constructs a symbol table with sufficient size for "estimated_elements". - /*! the "estimated_elements" dictates how large the symbol table's key space is. the - number of keys that can be stored without collisions (assuming perfect distribution - from the hash function) will be close to the number of elements specified. */ - symbol_table(int estimated_elements = 100); - - symbol_table(const symbol_table &to_copy); - - ~symbol_table(); - - int symbols() const; - //!< returns the number of symbols listed in the table. - - int estimated_elements() const; - //!< returns the number of symbols the table is optimized for. - - void rehash(int estimated_elements); - //!< resizes underlying table to support "estimated_elements". - - void hash_appropriately(int estimated_elements); - //!< Resizes the number of table slots to have space for "estimated_elements". - - symbol_table &operator =(const symbol_table &to_copy); - - basis::outcome add(const basis::astring &name, const contents &storage); - //!< Enters a symbol name into the table along with some contents. - /*!< If the name already exists in the table, then the previous contents - are replaced with these, but EXISTING is returned. If this is a new entry, - then IS_NEW is returned instead. */ - - const basis::astring &name(int index) const; - //!< returns the name held at the "index". - /*!< if the index is invalid, then a bogus name is returned. */ - - void names(string_set &to_fill) const; - //!< returns the names of all the symbols currently held. - - contents &operator [] (int index); - //!< provides access to the symbol_table's contents at the "index". - const contents &operator [] (int index) const; - //!< provides a constant peek at the contents at the "index". - - const contents &get(int index) const { return operator[](index); } - //!< named equivalent for the bracket operator. - contents &use(int index) { return operator[](index); } - //!< named equivalent for the bracket operator. - - contents *find(const basis::astring &name) const; - //!< returns the contents held for "name" or NIL if it wasn't found. - - contents *find(const basis::astring &name, - basis::string_comparator_function *comparator) const; - //!< Specialized search via a comparison method "comparator". - /*!< Searches for a symbol by its "name" but uses a special comparison - function "comparator" to determine if the name is really equal. This - method is by its nature slower than the main find method, since all buckets - must be searched until a match is found. It is just intended to provide - extensibility. */ - - int dep_find(const basis::astring &name) const; - //!< Searches for a symbol by its "name". - /*!< Returns the index or NOT_FOUND. NOTE: this is deprecated; it is far - faster to use the first find method above. */ - - //! Locates the symbol at position "index" and stores it to the parameters. - /*! retrieve() accesses the "index"th symbol in the table and returns the - held contents for it as well as its name. if the outcome is OKAY, then - the returned information is valid. otherwise, the search failed. */ - basis::outcome retrieve(int index, basis::astring &symbol_name, contents &contains) const; - - basis::outcome whack(const basis::astring &name); - //!< removes a symbol from the table. - /*!< this succeeds only if the symbol name is present in the table. */ - - basis::outcome zap_index(int index); - //!< zaps the entry at the specified index. slower than whack(). - - void reset(); // *_symbol_list; //!< our table of symbols. - internal_symbol_indexer *_tracker; //!< indexed lookup support. - - internal_symbol_info *get_index(int index) const; - //!< returns the info item at "index" or NIL. -}; - -////////////// - -template -bool symbol_table_compare(const symbol_table &a, - const symbol_table &b); - //!< returns true if table "a" and table "b" have the same contents. - -////////////// - -// implementations below... - -//#define DEBUG_SYMBOL_TABLE - // uncomment for noisier debugging. - -#ifdef DEBUG_SYMBOL_TABLE - #undef LOG - #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) -#endif - -////////////// - -// this structure keeps track of our symbol table elements. - -template -class internal_symbol_info -{ -public: - contents _content; // the object provided by the user. - basis::astring _name; // symbol's name. - - internal_symbol_info(const contents &content, const basis::astring &name) - : _content(content), _name(name) {} -}; - -////////////// - -// this is our tracking system that allows us to offer array like services. -// each symbol held has an address as one of our wrappers. thus we can -// list those addresses in a hash table along with listing the contents -// in the main hash table. the pointer_hash gives us a list of ids set, so -// we can have some ordering for the items in the table. - -template -class internal_pointer_hider -{ -public: - internal_symbol_info *_held; - - internal_pointer_hider(internal_symbol_info *held) : _held(held) {} -}; - -template -class internal_symbol_indexer -: public pointer_hash > -{ -public: - internal_symbol_indexer(int elems) - : pointer_hash >(elems) {} -}; - -////////////// - -// this object maintains the symbol table's contents. - -template -class internal_symbol_list -: public string_hash > -{ -public: - internal_symbol_list(int elems) - : string_hash >(elems) {} -}; - -////////////// - -// the main algorithms class implementing the external interface. - -#undef static_class_name -#define static_class_name() "symbol_table" - -template -symbol_table::symbol_table(int estimated_elements) -: _symbol_list(new internal_symbol_list(estimated_elements)), - _tracker(new internal_symbol_indexer(estimated_elements)) -{} - -template -symbol_table::symbol_table(const symbol_table &to_copy) -: _symbol_list(new internal_symbol_list - (to_copy._symbol_list->estimated_elements())), - _tracker(new internal_symbol_indexer(to_copy.estimated_elements())) -{ *this = to_copy; } - -template -symbol_table::~symbol_table() -{ - WHACK(_symbol_list); - WHACK(_tracker); -} - -template -int symbol_table::estimated_elements() const -{ return _symbol_list->estimated_elements(); } - -template -void symbol_table::rehash(int estimated_elements) -{ - _symbol_list->rehash(estimated_elements); - _tracker->rehash(estimated_elements); -} - -template -void symbol_table::hash_appropriately(int new_elements) -{ rehash(new_elements); } - -template -int symbol_table::symbols() const -{ return _tracker->ids().elements(); } - -template -symbol_table &symbol_table:: - operator =(const symbol_table &to_copy) -{ - if (this == &to_copy) return *this; - reset(); - for (int i = 0; i < to_copy.symbols(); i++) { - internal_symbol_info *info = to_copy.get_index(i); - if (info) { - internal_symbol_info *new_info - = new internal_symbol_info(*info); - _symbol_list->add(info->_name, new_info); - internal_pointer_hider *new_track = - new internal_pointer_hider(new_info); - _tracker->add(new_info, new_track); - } - } - return *this; -} - -template -void symbol_table::reset() -{ - _symbol_list->reset(); - _tracker->reset(); -} - -template -const basis::astring &symbol_table::name(int index) const -{ - bounds_return(index, 0, symbols() - 1, basis::bogonic()); - return get_index(index)->_name; -} - -template -void symbol_table::names(string_set &to_fill) const -{ - to_fill.reset(); - for (int i = 0; i < symbols(); i++) - to_fill += get_index(i)->_name; -} - -template -internal_symbol_info *symbol_table:: - get_index(int index) const -{ - bounds_return(index, 0, symbols() - 1, NIL); - return _tracker->find(_tracker->ids()[index])->_held; -} - -template -const contents &symbol_table::operator [] (int index) const -{ - bounds_return(index, 0, symbols() - 1, basis::bogonic()); - internal_symbol_info *found = get_index(index); - if (!found) return basis::bogonic(); - return found->_content; -} - -template -contents &symbol_table::operator [] (int index) -{ - bounds_return(index, 0, symbols() - 1, basis::bogonic()); - internal_symbol_info *found = get_index(index); - if (!found) return basis::bogonic(); - return found->_content; -} - -template -contents *symbol_table::find(const basis::astring &name) const -{ -// FUNCDEF("find [name]"); - internal_symbol_info *found = _symbol_list->find(name); - if (!found) return NIL; - return &found->_content; -} - -template -int symbol_table::dep_find(const basis::astring &name) const -{ - internal_symbol_info *entry = _symbol_list->find(name); - if (!entry) return basis::common::NOT_FOUND; - - for (int i = 0; i < symbols(); i++) { - if (_tracker->ids()[i] == entry) return i; - } - return basis::common::NOT_FOUND; // this is bad; it should have been found. -} - -template -struct sym_tab_apply_data -{ - basis::string_comparator_function *_scf; - contents *_found; - basis::astring _to_find; - - sym_tab_apply_data() : _scf(NIL), _found(NIL) {} -}; - -template -bool sym_tab_finder_apply(const basis::astring &key, contents ¤t, - void *data_link) -{ -#ifdef DEBUG_SYMBOL_TABLE - FUNCDEF("sym_tab_finder_apply"); -#endif - sym_tab_apply_data *package - = (sym_tab_apply_data *)data_link; -#ifdef DEBUG_SYMBOL_TABLE - LOG(basis::astring(" checking ") + key); -#endif - bool equals = package->_scf(key, package->_to_find); - if (equals) { - package->_found = ¤t; - return false; // done. - } - return true; // keep going. -} - -template -contents *symbol_table::find(const basis::astring &name, - basis::string_comparator_function *comparator) const -{ -#ifdef DEBUG_SYMBOL_TABLE - FUNCDEF("find [comparator]"); -#endif - if (!comparator) return find(name); // devolve to simplified call. -#ifdef DEBUG_SYMBOL_TABLE - LOG(basis::astring("looking for ") + name); -#endif - sym_tab_apply_data pack; - pack._to_find = name; - pack._scf = comparator; - // iterate across all the items in the hash. - _symbol_list->apply(sym_tab_finder_apply, &pack); - return pack._found; -} - -template -basis::outcome symbol_table::add(const basis::astring &name, const contents &to_add) -{ -// FUNCDEF("add"); - internal_symbol_info *found = _symbol_list->find(name); - if (!found) { - // not there already. - internal_symbol_info *new_item - = new internal_symbol_info(to_add, name); - _symbol_list->add(name, new_item); - internal_pointer_hider *new_track = - new internal_pointer_hider(new_item); - _tracker->add(new_item, new_track); - return basis::common::IS_NEW; - } - // overwrite the existing contents. - found->_content = to_add; - return basis::common::EXISTING; -} - -template -basis::outcome symbol_table::zap_index(int index) -{ - basis::astring dead_name = name(index); - return whack(dead_name); -} - -template -basis::outcome symbol_table::whack(const basis::astring &name) -{ -// FUNCDEF("whack"); - internal_symbol_info *sep_ind = _symbol_list->find(name); - // we need this pointer so we can find the tracker entry easily. - bool found_it = _symbol_list->zap(name); - if (found_it) { - _tracker->zap(sep_ind); - } - return found_it? basis::common::OKAY : basis::common::NOT_FOUND; -} - -template -basis::outcome symbol_table::retrieve(int index, basis::astring &name, - contents &got) const -{ - bounds_return(index, 0, symbols() - 1, basis::common::NOT_FOUND); - internal_symbol_info *found = get_index(index); - name = found->_name; - got = found->_content; - return basis::common::OKAY; -} - -////////////// - -template -bool symbol_table_compare(const symbol_table &a, - const symbol_table &b) -{ -// FUNCDEF("symbol_table_compare"); - - string_set names_a; - a.names(names_a); - string_set names_b; - b.names(names_b); - if (names_a != names_b) return false; - - for (int i = 0; i < names_a.elements(); i++) { - const basis::astring ¤t_key = names_a[i]; - const contents *a_value = a.find(current_key); - const contents *b_value = b.find(current_key); - if (!a_value || !b_value) continue; // not good. - if (*a_value != *b_value) return false; - } - return true; -} - -#ifdef DEBUG_SYMBOL_TABLE - #undef LOG -#endif - -#undef static_class_name - -} //namespace. - -#endif - - diff --git a/core/library/structures/unique_id.h b/core/library/structures/unique_id.h deleted file mode 100644 index 43f87183..00000000 --- a/core/library/structures/unique_id.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef UNIQUE_ID_CLASS -#define UNIQUE_ID_CLASS - -/*****************************************************************************\ -* * -* Name : unique_id * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1999-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace structures { - -//! Provides an abstraction for the responsibilities of a unique identifier. -/*! - These are generally used as a way of masking the underlying ID object while - providing some equality comparisons. It is especially useful when the - underlying object is not itself an object, but just a simple type. -*/ - -template -class unique_id : public virtual basis::equalizable -{ -public: - unique_id(uniquifier initial_value) : _id(initial_value) {} - //!< Constructs a unique id from the "initial_value". - - unique_id(const unique_id &to_copy) { *this = to_copy; } - //!< Constructs a unique id as a copy of the "to_copy" object. - - ~unique_id() {} - - virtual bool equal_to(const equalizable &to_compare) const { - const unique_id *cast = dynamic_cast *>(&to_compare); - if (!cast) throw "error: unique_id::==: unknown type"; - return cast->_id == _id; - } - - //! Returns true if the held id is the same as "to_compare". - /*! The templated uniquifying type absolutely must provide an equality - operator (==) and an assignment operator (=). */ - bool operator == (const unique_id &to_compare) const - { return _id == to_compare._id; } - - //! Sets this id to be the same as "to_copy". - unique_id & operator = (const unique_id &to_copy) - { if (this != &to_copy) _id = to_copy._id; return *this; } - - uniquifier raw_id() const { return _id; } - //!< Returns the held identifier in its native form. - - void set_raw_id(uniquifier new_value) { _id = new_value; } - //!< Sets the held identifier to "new_value". - -private: - uniquifier _id; //!< the held object in its native form. -}; - -////////////// - -//! A unique identifier class that supports sorting. -/*! - The orderable version can be compared for magnitude and permits sorting - the ids based on the underlying type. The underlying type must implement - at least the less than operator. -*/ - -template -class orderable_unique_id : public unique_id -{ -public: - orderable_unique_id(const uniquifier &initial_value) - : unique_id(initial_value) {} - orderable_unique_id(const unique_id &initial_value) - : unique_id(initial_value) {} - ~orderable_unique_id() {} - - /*! the "uniquifier" type absolutely must provide a less than operator (<) - and it must meet the requirements of the "unique_id" template. */ - bool operator < (const unique_id &to_compare) const - { return this->raw_id() < to_compare.raw_id(); } -}; - -////////////// - -//! A unique identifier based on integers. - -class unique_int : public unique_id -{ -public: - unique_int(int initial = 0) : unique_id(initial) {} - //!< implicit default for "initial" of zero indicates bogus id. - - bool operator ! () const { return raw_id() == 0; } - //!< provides a way to test whether an id is valid. - /*!< This uses the implicit assumption that a zero id is invalid or - unassigned. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/structures/version_record.cpp b/core/library/structures/version_record.cpp deleted file mode 100644 index f3bfc659..00000000 --- a/core/library/structures/version_record.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/*****************************************************************************\ -* * -* Name : version structures: version, version_record * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "string_array.h" -#include "version_record.h" - -#include - -#include - -using namespace basis; - -namespace structures { - -void *version::__global_module_handle() -{ - static void *__mod_hand = 0; - return __mod_hand; -} - -version::version() -: _components(new string_array) -{ - set_component(MAJOR, "0"); - set_component(MINOR, "0"); - set_component(REVISION, "0"); - set_component(BUILD, "0"); -} - -version::version(const string_array &version_info) -: _components(new string_array(version_info)) -{ - for (int i = 0; i < _components->length(); i++) { - if (!(*_components)[i]) { - // this component is blank; replace it with a zero. - (*_components)[i] = "0"; - } - } -} - -version::version(const astring &formatted_string) -: _components(new string_array) -{ - astring verstring = formatted_string; - // scan through and replace bogus bits with reasonable bits. - for (int j = 0; j < verstring.length(); j++) { - if (verstring[j] == ',') verstring[j] = '.'; - // replace commas with periods. - } - // locate the pieces of the version string. - for (int i = 0; i < verstring.length(); i++) { - int perindy = verstring.find('.', i); - if (negative(perindy)) { - if (verstring.length() - i > 0) { - // add any extra bits after the last period in the string. - *_components += verstring.substring(i, verstring.end()); - } - break; - } - // we found a period, so include anything between the current position and - // that as a component. - *_components += verstring.substring(i, perindy - 1); - i = perindy; - // set i to be at the next period; it will be incremented past that. - } -} - -version::version(int maj, int min, int rev, int build) -: _components(new string_array) -{ - *this = version(a_sprintf("%u.%u.%u.%u", basis::un_int(maj), basis::un_int(min), basis::un_int(rev), - basis::un_int(build))); -} - -version::version(const version &to_copy) -: _components(new string_array) -{ *this = to_copy; } - -version::~version() { WHACK(_components); } - -version &version::operator =(const version &to_copy) -{ - if (this != &to_copy) *_components = *to_copy._components; - return *this; -} - -astring version::text_form() const { return flex_text_form(); } - -int version::components() const { return _components->length(); } - -int version::v_major() const -{ return int(get_component(MAJOR).convert(0)); } - -int version::v_minor() const -{ return int(get_component(MINOR).convert(0)); } - -int version::v_revision() const -{ return int(get_component(REVISION).convert(0)); } - -int version::v_build() const -{ return int(get_component(BUILD).convert(0)); } - -astring version::get_component(int index) const -{ - bounds_return(index, 0, _components->length() - 1, "0"); - return (*_components)[index]; -} - -void version::set_component(int index, const astring &to_set) -{ - if (_components->length() <= index) - _components->resize(index + 1); - if (to_set.t()) - (*_components)[index] = to_set; - else - (*_components)[index] = "0"; -} - -bool version::equal_to(const equalizable &to_test) const -{ - const version *cast = cast_or_throw(to_test, *this); - return *_components == *cast->_components; -} - -bool version::less_than(const orderable &to_test) const -{ - const version *cast = cast_or_throw(to_test, *this); - if (v_major() < cast->v_major()) return true; - else if (v_major() > cast->v_major()) return false; - if (v_minor() < cast->v_minor()) return true; - else if (v_minor() > cast->v_minor()) return false; - if (v_revision() < cast->v_revision()) return true; - else if (v_revision() > cast->v_revision()) return false; - if (v_build() < cast->v_build()) return true; - return false; -} - -bool version::compatible(const version &to_test) const -{ -//fix to be more general - return (v_major() == to_test.v_major()) - && (v_minor() == to_test.v_minor()) - && (v_revision() == to_test.v_revision()); -} - -bool version::bogus() const -{ return !v_major() && !v_minor() && !v_revision() && !v_build(); } - -#define SEPARATE \ - if (style != DOTS) to_return += ", "; \ - else to_return += "." - -astring version::flex_text_form(version_style style, int where) const -{ - // note: the conversions below are to ensure proper treatment of the - // size of the int16 types by win32. - astring to_return; - - if (style == DETAILED) to_return += "major "; - astring temp = get_component(MAJOR); - if (!temp) temp = "0"; - to_return += temp; - if (where == MAJOR) return to_return; - SEPARATE; - - if (style == DETAILED) to_return += "minor "; - temp = get_component(MINOR); - if (!temp) temp = "0"; - to_return += temp; - if (where == MINOR) return to_return; - SEPARATE; - - if (style == DETAILED) to_return += "revision "; - temp = get_component(REVISION); - if (!temp) temp = "0"; - to_return += temp; - if (where == REVISION) return to_return; - SEPARATE; - - if (style == DETAILED) to_return += "build "; - temp = get_component(BUILD); - if (!temp) temp = "0"; - to_return += temp; - - // other components don't have handy names. - if (where > BUILD) { - for (int i = BUILD + 1; i < where; i++) { - SEPARATE; - if (style == DETAILED) to_return += a_sprintf("part_%d ", i + 1); - temp = get_component(i); - if (!temp) temp = "0"; - to_return += temp; - } - } - - return to_return; -} - -version version::from_text(const astring &to_convert) -{ return version(to_convert); } - -int version::packed_size() const -{ - return _components->packed_size(); -} - -void version::pack(byte_array &target) const -{ _components->pack(target); } - -bool version::unpack(byte_array &source) -{ - if (!_components->unpack(source)) return false; - return true; -} - -////////////// - -#define VR_NEWLINE to_return += "\n" - -version_record::~version_record() -{} - -astring version_record::text_form() const -{ - astring to_return; - to_return += "Description: "; - to_return += description; - VR_NEWLINE; - to_return += "File Version: "; - to_return += file_version.text_form(); - VR_NEWLINE; - to_return += "Internal Name: "; - to_return += internal_name; - VR_NEWLINE; - to_return += "Original Name: "; - to_return += original_name; - VR_NEWLINE; - to_return += "Product Name: "; - to_return += product_name; - VR_NEWLINE; - to_return += "Product Version: "; - to_return += product_version.text_form(); - VR_NEWLINE; - to_return += "Company Name: "; - to_return += company_name; - VR_NEWLINE; - to_return += "Copyright: "; - to_return += copyright; - VR_NEWLINE; - to_return += "Trademarks: "; - to_return += trademarks; - VR_NEWLINE; - - return to_return; -} - -} //namespace. - diff --git a/core/library/structures/version_record.h b/core/library/structures/version_record.h deleted file mode 100644 index 55ffed35..00000000 --- a/core/library/structures/version_record.h +++ /dev/null @@ -1,218 +0,0 @@ -#ifndef VERSION_STRUCTURE_GROUP -#define VERSION_STRUCTURE_GROUP - -/*****************************************************************************\ -* * -* Name : version & version_record * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -/*! @file version_record.h - Note that this header is tuned for forward declaration of the version - and version_record objects; this is a better approach than including this - somewhat heavy header file in other header files. -*/ - -#include "string_array.h" - -#include -#include - -namespace structures { - -//! Holds a file's version identifier. -/*! - The version structures can be used in any of our components because - they're not platform specific. They maintain information about a file - that is part of the released product. -*/ - -class version : public virtual basis::packable, public virtual basis::orderable -{ -public: - version(); //!< constructs a blank version. - - version(const structures::string_array &version_info); - //!< constructs a version from a list of strings that form the components. - /*!< note that if a component is an empty string, it is changed to be a - zero ("0"). */ - - version(const basis::astring &formatted_string); - //!< the version components will be parsed from the "formatted_string". - /*!< the version component separator is the period ('.') or the comma (',') - character. */ - - version(int major, int minor, int rev = 0, int build = 0); - //!< constructs a win32 style version structure from the information. - - version(const version &to_copy); - //!< constructs a copy of "to_copy". - - virtual ~version(); - - version &operator =(const version &to_copy); - //!< assigns this to the "to_copy". - - DEFINE_CLASS_NAME("version"); - - virtual basis::astring text_form() const; - - bool equal_to(const equalizable &to_test) const; - //!< compares two versions for exact equality. - /*!< to perform a check of win32 build compatibility, use the - compatible() method. */ - - bool less_than(const orderable &to_test) const; - //!< reports if this version is less than "to_test". - /*!< supplies the other operator needed for the full set of comparisons - (besides equality). the basis namespace provides templates for the rest - of the comparison operators in . */ - - int components() const; - //!< reports the number of components that make up this version. - - basis::astring get_component(int index) const; - //!< returns the component at the specified index. - /*!< note that if an entry is an empty string, then a string with zero - in it is returned ("0"). */ - - void set_component(int index, const basis::astring &to_set); - //!< sets the component at "index" to "to_set". - /*!< an empty string for "to_set" is turned into a zero. */ - - enum version_places { MAJOR, MINOR, REVISION, BUILD }; - //!< these are names for the first four components of the version. - /*!< there may be more components than four on some platforms. */ - - enum version_style { DOTS, COMMAS, DETAILED }; - //!< different ways that a version object can be displayed. - /*!< DOTS is in the form "1.2.3.4" - COMMAS is in the form "1, 2, 3, 4" - DETAILED is in the form "major 1, minor 2, ..." - */ - - basis::astring flex_text_form(version_style style = DOTS, int including = -1) const; - //!< returns a textual form of the version number. - /*!< the place passed in "including" specifies how much of the version - to print, where a negative number means all components. for example, if - "including" is MINOR, then only the first two components (major and minor - components) are printed. */ - - static version from_text(const basis::astring &to_convert); - //!< returns a version structure parsed from "to_convert". - - virtual int packed_size() const; - virtual void pack(basis::byte_array &target) const; - virtual bool unpack(basis::byte_array &source); - - ////////////// - - // the following methods mainly help on win32 platforms, where the version - // components are always simple short integers. there are only ever four - // of these. if one adheres to that same scheme on other platforms, then - // these functions may be helpful. otherwise, with the mixed alphanumeric - // versions one sees on unix, these are not so great. - - int v_major() const; - //!< major version number. - /*!< major & minor are the most significant values for a numerical version. - these are the familiar numbers often quoted for software products, like - "jubware version 8.2". */ - int v_minor() const; - //!< minor version number. - int v_revision() const; - //!< revision level. - /*!< in the hoople code and the clam system, this number is changed for - every new build. when two versions of a file are the same in major, - minor and revision numbers, then they are said to be compatible. for - those using this version scheme, it asserts that dll compatibility has not - been broken if one swaps those two files in an installation. after the - swap, any components that are dependent on the dll must all link properly - against the replacement file. when in doubt, increment the version number. - some folks automatically increment the revision level every week. */ - int v_build() const; - //!< build number. - /*!< this number is not considered important when comparing file - compatibility. the compatible() method always returns true if two files - differ only in the "build" number (rather than major, minor or revision). - this allows patches to be created with a newer (larger) build number, but - still link fine with existing dlls. since the file is distinguished by - more than just its time stamp, it allows changes to an installation to be - tracked very precisely. some folks keep a catalog of patched components - for each software release and index the patch details by the different - build numbers. */ - - bool compatible(const version &that) const; - //!< returns true if this is compatible with "that" version on win32. - /*!< that means that all version components are the same except for the - last one, the build number. we allow the build numbers to fluctuate so - that patched components can be installed without causing version - complaints. */ - - bool bogus() const; - //!< returns true if the version held here is clearly bogus. - /*!< this means that all four numbers are zero. */ - -////////////// - - static void *__global_module_handle(); - //!< a static resource required to identify the actual win32 module that this lives in. - /*!< this handle is stored statically when the libraries are started up. it records - the handle of the module they belong to for later use in checking versions. */ - -//hmmm: storage here is still missing! - -private: - structures::string_array *_components; //!< our list of version components. -}; - -////////////// - -//! Holds all information about a file's versioning. -/*! Not all of these fields are meaningful on every platform. */ - -class version_record : public virtual basis::root_object -{ -public: - virtual ~version_record(); - - DEFINE_CLASS_NAME("version_record"); - - basis::astring text_form() const; - // returns a view of the fields in this record. - - // these describe a particular file: - basis::astring description; // the purpose of this file. - version file_version; // the version number for this file. - basis::astring internal_name; // the internal name of the file. - basis::astring original_name; // name of file before possible renamings. - - // these describe the project that the file belongs to: - basis::astring product_name; // the product this file belongs to. - version product_version; // the version of the product. - - // these describe the creators of the file: - basis::astring company_name; // name of the company that created the file. - - // legal matters: - basis::astring copyright; // copyright info for this file. - basis::astring trademarks; // trademarks related to the file. - - // extra pieces not stored in record (yet). - basis::astring web_address; // the location of the company on the web. -}; - -////////////// - -} //namespace. - -#endif - diff --git a/core/library/tests_algorithms/makefile b/core/library/tests_algorithms/makefile deleted file mode 100644 index 46f442b3..00000000 --- a/core/library/tests_algorithms/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_algorithms -TYPE = test -TARGETS = test_sorts.exe -DEFINITIONS += USE_HOOPLE_DLLS -LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ - structures textual timely filesystem structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_algorithms/test_sorts.cpp b/core/library/tests_algorithms/test_sorts.cpp deleted file mode 100644 index fefcb2e5..00000000 --- a/core/library/tests_algorithms/test_sorts.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* -* Name : test_sorts -* Author : Chris Koeritz -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace algorithms; -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -const int MAX_ELEMENTS = 1200; - -const int MAX_VALUE = 28000; - -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), to_print) - -class test_sorts : virtual public unit_base, virtual public application_shell -{ -public: - test_sorts() : application_shell() {} - DEFINE_CLASS_NAME("test_sorts"); - virtual int execute(); -}; - -int test_sorts::execute() -{ - FUNCDEF("execute"); - - int *list = new int[MAX_ELEMENTS]; - for (int i = 0; i < MAX_ELEMENTS; i++) - list[i] = randomizer().inclusive(0, MAX_VALUE); - -//astring ret; -//for (int i = 0; i < MAX_ELEMENTS; i++) ret += a_sprintf("%d ", list[i]); -//LOG(ret); -//LOG("-------------"); - - // check a normal sort. - shell_sort(list, MAX_ELEMENTS); - int last = -1; - for (int j = 0; j < MAX_ELEMENTS; j++) { - ASSERT_FALSE(list[j] < last, "ordering check - list should be ordered at first check"); - last = list[j]; - } - - // re-randomize the list. - for (int i = 0; i < MAX_ELEMENTS; i++) - list[i] = randomizer().inclusive(0, MAX_VALUE); - - // check a reversed sort. - shell_sort(list, MAX_ELEMENTS, true); - last = MAX_VALUE + 100; // past the maximum we'll include in the list. - for (int j = 0; j < MAX_ELEMENTS; j++) { - ASSERT_FALSE(list[j] > last, "ordering check - list should be ordered at second check"); - last = list[j]; - } - - // clean up now. - delete [] list; - - return final_report(); -} - -HOOPLE_MAIN(test_sorts, ) - diff --git a/core/library/tests_basis/checkup.cpp b/core/library/tests_basis/checkup.cpp deleted file mode 100644 index cf0a53bb..00000000 --- a/core/library/tests_basis/checkup.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Name : checkup -* Author : Chris Koeritz -** -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "checkup.h" - -#include -#include - -using namespace basis; -using namespace loggers; -using namespace unit_test; - -namespace system_checkup { - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT testing -#undef static_class_name -#define static_class_name() astring("system_checkup") - -bool check_system_characteristics(unit_base &testing) -{ - FUNCDEF("check_system_characteristics") - // a big assumption is that the size of an unsigned character is just - // one byte. if this is not true, probably many things would break... - int byte_size = sizeof(abyte); - ASSERT_EQUAL(byte_size, 1, "byte size should be 1 byte"); - int int16_size = sizeof(int16); - ASSERT_FALSE( (sizeof(uint16) != int16_size) || (int16_size != 2), - "uint16 size should be 2 bytes"); - int uint_size = sizeof(un_int); -//hmmm: the checks are actually redundant... - ASSERT_FALSE( (uint_size != sizeof(int)) || (uint_size != 4), - "un_int size should be 2 bytes"); - // all tests successfully passed. - return true; -} - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*this) -#undef static_class_name - -} // namespace - diff --git a/core/library/tests_basis/checkup.h b/core/library/tests_basis/checkup.h deleted file mode 100644 index 8d2b47c3..00000000 --- a/core/library/tests_basis/checkup.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CHECKUP_GROUP -#define CHECKUP_GROUP - -/* -* Name : checkup -* Author : Chris Koeritz -* Purpose: -* Checks that certain critical properties are upheld by the runtime -* environment. This could be invoked at the beginning of a main program to -* check these characteristics once before continuing execution. -** -* Copyright (c) 1990-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include - -namespace system_checkup { - - bool check_system_characteristics(unit_test::unit_base &testing); - // used to verify that this compilation system possesses some desired - // characteristics. true is returned if everything checks out, and false - // is returned if some assumption proves untrue. - -} - -#endif - diff --git a/core/library/tests_basis/makefile b/core/library/tests_basis/makefile deleted file mode 100644 index 16165886..00000000 --- a/core/library/tests_basis/makefile +++ /dev/null @@ -1,14 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_basis -TYPE = test -SOURCE = checkup.cpp -TARGETS = test_array.exe test_boilerplate.exe test_mutex.exe test_string.exe \ - test_system_preconditions.exe -DEFINITIONS += USE_HOOPLE_DLLS -LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ - structures textual timely filesystem structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_basis/test_array.cpp b/core/library/tests_basis/test_array.cpp deleted file mode 100644 index e7d04a6b..00000000 --- a/core/library/tests_basis/test_array.cpp +++ /dev/null @@ -1,929 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_array * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//#define DEBUG_ARRAY - // when this flag is turned on, extra checking will be done in the allocator. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -///#include //temp - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace timely; -using namespace unit_test; - -//const float MAX_TEST_DURATION_ms = 28 * SECOND_ms; -const float MAX_TEST_DURATION_ms = 200; - // each of the tests calling on the templated tester will take this long. - -const int MAX_SIMULTANEOUS_OBJECTS = 42; // the maximum arrays tested. - -const int MIN_OBJECT = -30; // the smallest array we'll create. -const int MAX_OBJECT = 98; // the largest array we'll create. - -const int MIN_BLOCK = 100; // the smallest exemplar we'll use. -const int MAX_BLOCK = MAX_OBJECT * 2; // the largest exempler. - -//#define DEBUG_TEST_ARRAY - // uncomment for noisy version. - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -static chaos a_randomizer; - -////////////// - -class test_array : public application_shell, public unit_base -{ -public: - test_array() - : application_shell(), - unit_base() - { -//hmmm: should go into app shell -////SETUP_CONSOLE_LOGGER; -} - DEFINE_CLASS_NAME("test_array"); - virtual int execute(); - void dump_array(array &ar, const char *name); - void test_arrays_of_void_pointer(); - void test_iteration_speed(); - - template - void array_tester(test_array &ta, const contents &formal(bogus), basis::un_short flags); - -}; - -////////////// - -void test_array::dump_array(array &ar, const char *name) -{ -#ifdef DEBUG_TEST_ARRAY - FUNCDEF("dump_array"); - LOG(a_sprintf("array named \"%s\" has:", name)); - for (int i = 0; i < ar.length(); i++) { - LOG(a_sprintf("\t%4d: %d", i, (int)ar[i])); - } -#else - if (ar.length() && name) {} -#endif -} - -void test_array::test_arrays_of_void_pointer() -{ - FUNCDEF("void pointer test"); - const int MAX_VOID_ARRAY = 20; - array argh(MAX_VOID_ARRAY, NIL, byte_array::SIMPLE_COPY - | byte_array::EXPONE | byte_array::FLUSH_INVISIBLE); - array argh2(argh); - ASSERT_EQUAL(argh.length(), MAX_VOID_ARRAY, "check first array length"); - ASSERT_EQUAL(argh2.length(), MAX_VOID_ARRAY, "check copied array length"); - int wrong_counter = 0; - for (int o = 0; o < MAX_VOID_ARRAY; o++) - if (argh[o] != argh2[o]) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "compare array contents"); - - // fill it with values. - int starter; - for (int i = 0; i < MAX_VOID_ARRAY; i++) - argh[i] = (void *)(&starter + i); - dump_array(argh, "first version"); - - // check the values. - wrong_counter = 0; - for (int j = 0; j < MAX_VOID_ARRAY; j++) - if (argh[j] != (void *)(&starter + j) ) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "compare values that were set"); - - // add a constant to the values. - for (int k = 0; k < MAX_VOID_ARRAY; k++) - argh[k] = (void *)((int *)argh[k] + 23); - dump_array(argh, "second version"); - - // check assignment. - argh2 = argh; - wrong_counter = 0; - for (int n = 0; n < MAX_VOID_ARRAY; n++) - if (argh2[n] != (void *)(&starter + n + 23)) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "compare values that were assigned"); - - // now test that values are kept in place after rearrangement. - argh.zap(3, 4); - dump_array(argh, "third version"); - wrong_counter = 0; - for (int l = 0; l < 3; l++) - if (argh[l] != (void *)(&starter + l + 23)) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "zap low values test"); - wrong_counter = 0; - for (int m = 3; m < MAX_VOID_ARRAY - 2; m++) - if (argh[m] != (void *)(&starter + m + 2 + 23)) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "zap high values test"); -//hmmm: add more tests if void pointer arrays seem in question. -} - -////////////// - -static astring blank_string; - -// jethro is a simple object for containment below. -class jethro -{ -public: - jethro(const astring &i = blank_string) : _truck(i) {} - - astring _truck; - - bool operator ==(const jethro &tc) const { return tc._truck == _truck; } -}; - -////////////// - -// the test_content object is an object with proper copy constructor -// and assignment operator that also has deep contents. -class test_content -{ -public: - abyte _q; - astring _ted; - astring _jed; - int_array _ned; - - test_content(abyte q = 3) - : _q(q), _ted("bl"), _jed("orp"), - _ned(12, NIL) -/* _med(3, 2), - _red(2, 4) */ - { - for (int i = 0; i < _ned.length(); i++) - _ned[i] = -i; -/* - for (int r = 0; r < _med.rows(); r++) - for (int c = 0; c < _med.columns(); c++) - _med[r][c] = jethro(astring((r*c) % 256, 1)); - for (int j = 0; j < _red.rows(); j++) - for (int k = 0; k < _red.columns(); k++) - _red[j][k] = j * k; -*/ - } - - bool operator ==(const test_content &tc) const { - if (tc._q != _q) return false; - if (tc._ted != _ted) return false; - if (tc._jed != _jed) return false; - if (tc._ned.length() != _ned.length()) return false; - for (int i = 0; i < _ned.length(); i++) - if (tc._ned[i] != _ned[i]) return false; - -/* - if (tc._med.rows() != _med.rows()) return false; - if (tc._med.columns() != _med.columns()) return false; - for (int c = 0; c < _med.columns(); c++) - for (int r = 0; r < _med.rows(); r++) - if (tc._med.get(r, c) != _med.get(r, c)) return false; - - if (tc._red.rows() != _red.rows()) return false; - if (tc._red.columns() != _red.columns()) return false; - for (int j = 0; j < _red.rows(); j++) - for (int k = 0; k < _red.columns(); k++) - if (tc._red.get(j, k) != _red.get(j, k)) return false; -*/ - - return true; - } - - operator abyte() const { return _q; } -}; - -////////////// - -template -bool compare_arrays(const array &a, const array &b) -{ - if (a.length() != b.length()) return false; - for (int i = 0; i < a.length(); i++) - if (a[i] != b[i]) return false; - return true; -} - -////////////// - -// this is a templated test for arrays, with some requirements. the contents -// object must support a constructor that takes a simple byte, whether -// that's meaningful for the object or not. if your type to test doesn't -// have that, derive a simple object from it, give it that constructor, and -// then it can be used below. the object must also support comparison with -// == and != for this test to be used. it must also provide a conversion -// to integer that returns the value passed to the constructor. - -template -void test_array::array_tester(test_array &ta, const contents &formal(bogus), basis::un_short flags) -{ - FUNCDEF("array_tester"); - // the space that all training for arrays comes from. - contents *junk_space = new contents[MAX_OBJECT + MAX_BLOCK]; - for (int i = 0; i < MAX_OBJECT - 1; i++) - junk_space[i] = contents(a_randomizer.inclusive('a', 'z')); - junk_space[MAX_OBJECT + MAX_BLOCK - 1] = '\0'; - - array *testers[MAX_SIMULTANEOUS_OBJECTS]; - for (int c = 0; c < MAX_SIMULTANEOUS_OBJECTS; c++) { - // set up the initial array guys. - testers[c] = new array(a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT), - NIL, flags); - // copy the randomized junk space into the new object. - for (int i = 0; i < testers[c]->length(); i++) - testers[c]->put(i, junk_space[i]); - } - - // these are the actions we try out with the array during the test. - // the first and last elements must be identical to the first and last - // tests to perform. - enum actions { first, do_train = first, do_size, do_assign, - do_access, do_zap, do_resizer, do_shrink, do_reset, do_indices, - do_concatenating, do_concatenating2, do_subarray, do_insert, - do_overwrite, do_stuff, do_memory_paring, do_snarf, do_flush, - do_observe, last = do_observe }; - - time_stamp exit_time(MAX_TEST_DURATION_ms); - while (time_stamp() < exit_time) { - int index = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); - int choice = a_randomizer.inclusive(first, last); - switch (choice) { - case do_train: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_train")); -#endif - int new_size = a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT); - testers[index]->retrain(new_size, (contents *)junk_space); - int wrong_counter = 0; - for (int i = 0; i < new_size; i++) - if (junk_space[i] != (*testers[index])[i]) wrong_counter++; - ASSERT_EQUAL(wrong_counter, 0, "test contents after retrain"); - break; - } - case do_size: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_size")); -#endif - array old_version = *testers[index]; - bool at_front = bool(a_randomizer.inclusive(0, 1)); - int new_size = a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT); - bool smaller = new_size < old_version.length(); - int difference = absolute_value(new_size - old_version.length()); - testers[index]->resize(new_size, - at_front? old_version.NEW_AT_BEGINNING - : old_version.NEW_AT_END); - if (!smaller && difference) { - // neuter the contents in the new section so we can compare. this - // space is filled with whatever the object's constructor chooses. - if (at_front) { - for (int i = 0; i < difference; i++) - testers[index]->put(i, 'Q'); - } else { - for (int i = old_version.length(); - i < old_version.length() + difference; i++) - testers[index]->put(i, 'Q'); - } - } - // now compute an equivalent form of what the state should be. - array equivalent(0, NIL, flags); - if (at_front) { - if (smaller) { - equivalent = old_version.subarray(difference, - old_version.length() - 1); - } else { - array blank(difference, NIL, flags); - for (int i = 0; i < blank.length(); i++) - blank[i] = 'Q'; - equivalent = blank + old_version; - } - } else { - if (smaller) { - equivalent = old_version.subarray(0, old_version.length() - - difference - 1); - } else { - array blank(difference, NIL, flags); - for (int i = 0; i < blank.length(); i++) - blank[i] = 'Q'; - equivalent = old_version + blank; - } - } - ASSERT_EQUAL(equivalent.length(), testers[index]->length(), - "check length of resized form"); - ASSERT_TRUE(compare_arrays(*testers[index], equivalent), - "check contents of resized form"); - break; - } - case do_assign: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_assign")); -#endif - array arrh = *testers[index]; // copy old value. - int to_assign = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); - *testers[index] = *testers[to_assign]; - ASSERT_TRUE(compare_arrays(*testers[index], *testers[to_assign]), - "check result of assign copying array"); - *testers[to_assign] = arrh; // recopy contents to new place. - ASSERT_TRUE(compare_arrays(*testers[to_assign], arrh), - "check result of second assign"); - break; - } - case do_access: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_access")); -#endif - int start = a_randomizer.inclusive(0, testers[index]->length()); - int end = a_randomizer.inclusive(0, testers[index]->length()); - flip_increasing(start, end); - array accumulator(0, NIL, flags); - for (int i = start; i < end; i++) { - contents c = contents(a_randomizer.inclusive(1, 255)); - testers[index]->access()[i] = c; - accumulator += c; - } - for (int j = start; j < end; j++) - ASSERT_EQUAL(accumulator[j - start], (*testers[index])[j], - "comparison accessing at index must be equal"); - break; - } - case do_indices: { -#ifndef CATCH_ERRORS // only works if not catching errors. - #ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_indices")); - #endif - // does some tests on bad indices. - contents c1 = testers[index]->operator[](-50); - contents c2 = testers[index]->operator[](-MAX_OBJECT); - bool test = (c1 == c2); - ASSERT_TRUE(test, "invalid values should be the same"); - int tests = a_randomizer.inclusive(100, 500); - for (int i = 0; i < tests; i++) { - int indy = a_randomizer.inclusive(-1000, MAX_OBJECT * 3); - // testing if we can access without explosions. - contents c3 = testers[index]->operator[](indy); - contents c4 = c3; - ASSERT_EQUAL(c3, c4, "for do_indices, values should be the same"); - } -#endif - break; - } - case do_observe: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_observe")); -#endif - // tests contents returned by observe. - int start = a_randomizer.inclusive(0, testers[index]->length()); - int end = a_randomizer.inclusive(0, testers[index]->length()); - flip_increasing(start, end); - double total_1 = 0; - for (int i = start; i < end; i++) - total_1 += testers[index]->observe()[i]; - double total_2 = 0; - for (int j = end - 1; j >= start; j--) - total_2 += testers[index]->observe()[j]; - ASSERT_EQUAL(total_1, total_2, "totals should match up"); - break; - } - case do_resizer: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_resizer")); -#endif - // tests whether the array will reuse space when it should be able to. - array &arrh = *testers[index]; - // fill with known data. - int i; - for (i = 0; i < arrh.length(); i++) - arrh[i] = contents((i + 23) % 256); - // record the old starting point. - const contents *start = arrh.internal_block_start(); - int zap_amount = a_randomizer.inclusive(1, arrh.length() - 1); - // now take out a chunk from the array at the beginning. - arrh.zap(0, zap_amount - 1); - // test the contents. - for (i = 0; i < arrh.length(); i++) - ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), - "the resized form should have same contents after zap"); - // now add back in the space we ate. - arrh.resize(arrh.length() + zap_amount, arrh.NEW_AT_END); - // check the pointer; it should not have changed. - ASSERT_EQUAL(start, arrh.internal_block_start(), - "the resized form should have same start address"); - // test the contents again. they should start at zero and have the - // same zap_amount offset. the stuff past the original point shouldn't - // be tested since we haven't set it. - for (i = 0; i < arrh.length() - zap_amount; i++) - ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), - "the resized form should still have same contents"); - // now a test of all values through the zap_amount. - arrh.zap(0, zap_amount - 1); - for (i = 0; i < zap_amount; i++) { - arrh.resize(arrh.length() + 1, arrh.NEW_AT_END); - ASSERT_EQUAL(start, arrh.internal_block_start(), - "the slowly resized form should have same start address"); - } - // test the contents once more. they should start at zero and have - // double the zap_amount offset. the stuff past the original point - // shouldn't be tested since we haven't set it. - for (i = 0; i < arrh.length() - 2 * zap_amount; i++) - ASSERT_EQUAL(arrh[i], contents((i + 23 + 2 * zap_amount) % 256), - "the slowly resized form should have same contents"); - - // the tests below should be nearly identical to the ones above, but - // they use the NEW_AT_BEGINNING style of copying. - - // fill with known data. - for (i = 0; i < arrh.length(); i++) - arrh[i] = contents((i + 23) % 256); - // record the old starting point. - start = arrh.internal_block_start(); - zap_amount = a_randomizer.inclusive(1, arrh.length() - 1); - // now take out a chunk from the array at the beginning. - arrh.zap(0, zap_amount - 1); - // test the contents. - for (i = 0; i < arrh.length(); i++) - ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), - "the resized form with known data should have right contents"); - // now add back in the space we ate. - arrh.resize(arrh.length() + zap_amount, - arrh.NEW_AT_BEGINNING); - // check the pointer; it should not have changed. - ASSERT_EQUAL(start, arrh.internal_block_start(), - "the resized form with known data should have same start address"); - // test the contents again. they should start at zap_amount but have - // the same zap_amount offset. the stuff before the original point - // shouldn't be tested since we haven't set it. - for (i = zap_amount; i < arrh.length(); i++) - ASSERT_EQUAL(arrh[i], contents((i + 23) % 256), - "the known data resized form should have same contents"); - // now a test of all values through the zap_amount. - arrh.zap(0, zap_amount - 1); - for (i = 0; i < zap_amount; i++) { - arrh.resize(arrh.length() + 1, arrh.NEW_AT_BEGINNING); - ASSERT_EQUAL(start, arrh.internal_block_start(), - "the known data slowly resized form should have same start address"); - } - // test the contents once more. the zap_amount offset should stay the - // same since we clobbered the place we added originally, then added - // more space in. so we skip the first part still. - for (i = zap_amount; i < arrh.length(); i++) - ASSERT_EQUAL(arrh[i], contents((i + 23) % 256), - "the known data slowly resized form should have same contents"); - break; - } - case do_zap: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_zap")); -#endif - int start; - int end; - bool erroneous = false; - int chose = a_randomizer.inclusive(1, 100); - if (chose <= 90) { - // there's a ninety percent chance we pick a range that's valid. - start = a_randomizer.inclusive(0, testers[index]->length() - 1); - end = a_randomizer.inclusive(0, testers[index]->length() - 1); - } else if (chose <= 95) { - // and a 5 percent chance we pick to choose the zero index as our - // start; this gets at the code for fast zapping. - start = 0; - end = a_randomizer.inclusive(0, testers[index]->length() - 1); - } else { - // and a 5 percent chance we'll allow picking a bad index. the - // actual chance of picking a bad one is less. - erroneous = true; - start = a_randomizer.inclusive(-2, testers[index]->length() + 3); - end = a_randomizer.inclusive(-2, testers[index]->length() + 3); - } - flip_increasing(start, end); - array old_version = *testers[index]; - testers[index]->zap(start, end); - if (!erroneous) { - array old_head = old_version.subarray(0, start - 1); - array old_tail = old_version.subarray(end + 1, - old_version.length() - 1); - array equivalent = old_head + old_tail; - ASSERT_EQUAL(equivalent.length(), testers[index]->length(), - "the zapped form should not have erroneous length"); - ASSERT_TRUE(compare_arrays(*testers[index], equivalent), - "the zapped form should not have erroneous contents"); - } - break; - } - case do_reset: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_reset")); -#endif - int junk_start = a_randomizer.inclusive(MIN_BLOCK, MAX_BLOCK); - int junk_end = a_randomizer.inclusive(MIN_BLOCK, MAX_BLOCK); - flip_increasing(junk_start, junk_end); - int len = junk_end - junk_start + 1; - testers[index]->reset(len, (contents *)&junk_space[junk_start]); - ASSERT_EQUAL(testers[index]->length(), len, "reset should have proper length"); - for (int i = 0; i < len; i++) - ASSERT_EQUAL(testers[index]->get(i), junk_space[junk_start + i], - "reset should copy data"); - break; - } - case do_shrink: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_shrink")); -#endif - array garp = *testers[index]; - testers[index]->shrink(); - int new_diff = testers[index]->internal_real_length() - - testers[index]->length(); - ASSERT_TRUE(compare_arrays(garp, *testers[index]), "shrink should keep contents"); - // now force a real shrinkage. - if (testers[index]->length() < 5) continue; // need some room. - // pick an element to keep that is not first or last. - int kept = a_randomizer.inclusive(1, testers[index]->last() - 1); - // whack all but the lucky element. - testers[index]->zap(kept + 1, testers[index]->last()); - testers[index]->zap(0, kept - 1); - // shrink it again, now a big shrink. - testers[index]->shrink(); - // check the difference now. - new_diff = testers[index]->internal_real_length() - - testers[index]->length(); - ASSERT_FALSE(new_diff > 1, "massive shrink size should be correct"); - - // restore the original contents and block size. - *testers[index] = garp; - break; - } - case do_concatenating: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_concatenating")); -#endif - for (int i = 0; i < a_randomizer.inclusive(1, 20); i++) { - contents new_c = contents(a_randomizer.inclusive('a', 'z')); - testers[index]->concatenate(new_c); - ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), - "value should be equal after concatenate"); - } - int indy = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); - array flirpan = *testers[indy]; - int start = a_randomizer.inclusive(0, flirpan.length() - 1); - int end = a_randomizer.inclusive(0, flirpan.length() - 1); - flip_increasing(start, end); - flirpan = flirpan.subarray(start, end); - array grumzor = *testers[index]; // old copy. - testers[index]->concatenate(flirpan); - array bubula = grumzor + flirpan; - ASSERT_TRUE(compare_arrays(bubula, *testers[index]), - "contents should be correct after concatenate or concatenation"); - contents first_value; - contents second_value; - if (testers[index]->length() >= 1) - first_value = testers[index]->get(0); - if (testers[index]->length() >= 2) - second_value = testers[index]->get(1); - const int max_iters = a_randomizer.inclusive(1, 42); - for (int j = 0; j < max_iters; j++) { - contents new_c = contents(a_randomizer.inclusive('a', 'z')); - *testers[index] = *testers[index] + new_c; - // correct our value checks if new indices became available. - if (testers[index]->length() == 1) { - first_value = testers[index]->get(0); - } else if (testers[index]->length() == 2) { - second_value = testers[index]->get(1); - } - - ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), - "value should not be wrong after concatenation"); - - ASSERT_FALSE((testers[index]->length() >= 1) && (first_value != testers[index]->get(0)), - "first value should not be corrupted"); - ASSERT_FALSE((testers[index]->length() >= 2) && (second_value != testers[index]->get(1)), - "second value should not be corrupted"); - - *testers[index] += new_c; - // we don't have to correct the first value any more, but might have - // to correct the second. - if (testers[index]->length() == 2) { - second_value = testers[index]->get(1); - } - ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), - "value should be right after second concatenation"); - ASSERT_FALSE( (testers[index]->length() >= 1) && (first_value != testers[index]->get(0)), - "first value should be uncorrupted after second concat"); - ASSERT_FALSE((testers[index]->length() >= 2) && (second_value != testers[index]->get(1)), - "second value should be uncorrupted after second concat"); - } - break; - } - case do_concatenating2: { - // this tests the new concatenate content array method for array. -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_concatenating2")); -#endif - array &flirpan = *testers[index]; - int new_len = a_randomizer.inclusive(0, 140); - contents *to_add = new contents[new_len]; - for (int i = 0; i < new_len; i++) { - int rando = a_randomizer.inclusive(0, MAX_BLOCK - 1); - to_add[i] = junk_space[rando]; - } - int old_len = flirpan.length(); - flirpan.concatenate(to_add, new_len); - for (int i = 0; i < new_len; i++) { - ASSERT_EQUAL(flirpan[i + old_len], to_add[i], - "value should not be wrong after content array concatenation"); - } - delete [] to_add; // clean up. - break; - } - case do_subarray: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_subarray")); -#endif - array flirpan = *testers[index]; - int start = a_randomizer.inclusive(0, flirpan.length() - 1); - int end = a_randomizer.inclusive(0, flirpan.length() - 1); - flip_increasing(start, end); - flirpan = flirpan.subarray(start, end); - for (int i = 0; i < end - start; i++) - ASSERT_EQUAL(flirpan[i], testers[index]->get(i + start), - "subarray should produce right array"); - break; - } - case do_memory_paring: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_memory_paring")); -#endif - for (int i = 0; i < MAX_SIMULTANEOUS_OBJECTS; i++) { - // zap extra junk off so we bound the memory usage. - if (testers[i]->length() > MAX_OBJECT) { - testers[i]->zap(MAX_OBJECT, testers[i]->length() - 1); - testers[i]->shrink(); - } - } - break; - } - case do_snarf: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_snarf")); -#endif - array flirpan = *testers[index]; -// int start = a_randomizer.inclusive(0, flirpan.length() - 1); -// int end = a_randomizer.inclusive(0, flirpan.length() - 1); -// flip_increasing(start, end); -// flirpan = flirpan.subarray(start, end); - int rand_index = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); - if (index == rand_index) continue; // skip it; try again later. - array nugwort = *testers[rand_index]; - // perform a swap between two of our arrays. - array temp_hold(0, NIL, flags); - temp_hold.snarf(*testers[index]); - testers[index]->snarf(*testers[rand_index]); - testers[rand_index]->snarf(temp_hold); - // the copies should have flipped places now. check them. - ASSERT_EQUAL(flirpan.length(), testers[rand_index]->length(), - "snarf needs to produce right length at A"); - for (int i = 0; i < flirpan.length(); i++) - ASSERT_EQUAL(flirpan[i], testers[rand_index]->get(i), - "snarf needs to produce right array at A"); - ASSERT_EQUAL(nugwort.length(), testers[index]->length(), - "snarf needs to produce right length at B"); - for (int j = 0; j < nugwort.length(); j++) - ASSERT_EQUAL(nugwort[j], testers[index]->get(j), - "snarf needs to produce right array at B"); - break; - } - case do_flush: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_flush")); -#endif - array flirpan = *testers[index]; - -///fix up it up in it. - -/// flirpan.reset( - - - break; - } - - case do_insert: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_insert")); -#endif - array hold = *testers[index]; - int where = a_randomizer.inclusive(0, hold.last()); - int how_many = a_randomizer.inclusive(0, 25); - testers[index]->insert(where, how_many); - for (int i = 0; i < where; i++) - ASSERT_EQUAL(hold[i], testers[index]->get(i), - "should have good contents on left after insert"); - for (int j = where + how_many; j < testers[index]->length(); j++) - ASSERT_EQUAL(hold[j - how_many], testers[index]->get(j), - "should have good contents on right after insert"); - break; - } - case do_overwrite: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_overwrite")); -#endif - if (!testers[index]->length()) continue; - array hold = *testers[index]; - int index2 = index; - while (index2 == index) - index2 = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); - array &hold2 = *testers[index2]; - if (!hold2.length()) continue; - int write_indy = a_randomizer.inclusive(0, hold.last()); - int write_len = minimum(hold2.length(), (hold.length() - write_indy)); -// LOG(a_sprintf("len1 = %d len2 = %d wrindy=%d wrlen=%d", hold.length(), hold2.length(), write_indy, write_len)); - outcome ret = testers[index]->overwrite(write_indy, - *testers[index2], write_len); - ASSERT_EQUAL(ret.value(), common::OKAY, - astring("should not have had outcome=") + common::outcome_name(ret)); - for (int i = 0; i < write_indy; i++) - ASSERT_EQUAL(hold[i], testers[index]->get(i), - "should have good contents on left after overwrite"); - for (int j = write_indy; j < write_indy + write_len; j++) - ASSERT_EQUAL(hold2[j - write_indy], testers[index]->get(j), - "should have good contents in middle after overwrite"); - for (int k = write_indy + write_len; k < testers[index]->length(); k++) - ASSERT_EQUAL(hold[k], testers[index]->get(k), - "should have good contents on right after overwrite"); - break; - } - case do_stuff: { -#ifdef DEBUG_TEST_ARRAY - LOG(a_sprintf("do_stuff")); -#endif - array &hold = *testers[index]; - contents burgers[100]; - int stuff_len = a_randomizer.inclusive(0, hold.length()); - stuff_len = minimum(stuff_len, 100); - outcome ret = hold.stuff(stuff_len, burgers); - ASSERT_EQUAL(ret.value(), common::OKAY, - astring("should not have had outcome=") + common::outcome_name(ret)); - for (int i = 0; i < stuff_len; i++) - ASSERT_EQUAL(burgers[i], hold[i], - "should have good contents after stuff"); - break; - } - default: { - ASSERT_FALSE(true, "test cases should have no invalid choices!"); - break; - } - } - } - - // clean up. - delete [] junk_space; - for (int d = 0; d < MAX_SIMULTANEOUS_OBJECTS; d++) delete testers[d]; -} - -////////////// - -struct gerkin { int l; abyte *p; char *r; void *pffttt; }; - -gerkin borgia; - -class foop -{ -public: - virtual ~foop() {} - virtual gerkin *boorba() = 0; -}; - -class boop : public foop -{ -public: - virtual gerkin *boorba() { return &borgia; } -}; - -void test_array::test_iteration_speed() -{ - FUNCDEF("test_iteration_speed"); - const int MAX_CHUNK = 100000; - const int MAX_REPS = 20; - byte_array chunky(MAX_CHUNK); - - { - time_stamp start; - int sum = 0; - for (int j = 0; j < MAX_REPS; j++) { - for (int i = 0; i < MAX_CHUNK; i++) { - sum += chunky[i]; - } - } - int duration = int(time_stamp().value() - start.value()); - - a_sprintf message("iteration over %d elements took %d ms,\t" - "or %f ms per 1000 iters.", - MAX_CHUNK * MAX_REPS, duration, - double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0); -//base_logger &b = program_wide_logger::get(); - LOG(message); - } - - { - time_stamp start; - int sum = 0; - const abyte *head = chunky.observe(); - for (int j = 0; j < MAX_REPS; j++) { - for (int i = 0; i < MAX_CHUNK; i++) { - sum += head[i]; - } - } - int duration = int(time_stamp().value() - start.value()); - - LOG(a_sprintf("less-safe iteration over %d elements took %d ms,\tor %f ms per 1000 iters.", - MAX_CHUNK * MAX_REPS, duration, - double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0)); - } - { - time_stamp start; - boop tester; -// int sum = 0; - for (int j = 0; j < MAX_REPS; j++) { - for (int i = 0; i < MAX_CHUNK; i++) { -// chunky.modus().sampler(); - tester.boorba(); - } - } - int duration = int(time_stamp().value() - start.value()); - - LOG(a_sprintf("virtual-function-only over %d elements took %d ms,\tor %f ms per 1000 iters.", - MAX_CHUNK * MAX_REPS, duration, - double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0)); - } -} - -////////////// - -int test_array::execute() -{ - FUNCDEF("execute"); -#if 0 -// if enabled for the abusive type tests, then allow this one... - test_iteration_speed(); -// LOG(a_sprintf("did iteration test.")); -#endif - - int_array checking_start; - ASSERT_FALSE(checking_start.length(), - "int_array should not have contents from empty constructor"); - - double_plus bogus4 = float(12.32); - array_tester(*this, bogus4, - byte_array::SIMPLE_COPY | byte_array::EXPONE - | byte_array::FLUSH_INVISIBLE); -// LOG(a_sprintf("did float array test.")); - - int bogus2 = 39; - array_tester(*this, bogus2, - byte_array::SIMPLE_COPY | byte_array::EXPONE - | byte_array::FLUSH_INVISIBLE); -// LOG(a_sprintf("did int array test.")); - - test_content bogus3(12); - array_tester(*this, bogus3, byte_array::EXPONE - | byte_array::FLUSH_INVISIBLE); -// LOG(a_sprintf("did test_content array test.")); - - test_arrays_of_void_pointer(); -// LOG(a_sprintf("did void * array test.")); - - abyte bogus1 = 'c'; - array_tester(*this, bogus1, - byte_array::SIMPLE_COPY | byte_array::EXPONE - | byte_array::FLUSH_INVISIBLE); -// LOG(a_sprintf("did byte array test.")); - -//// LOG("array:: works for those functions tested."); - - return final_report(); -} - -HOOPLE_MAIN(test_array, ) - diff --git a/core/library/tests_basis/test_boilerplate.cpp b/core/library/tests_basis/test_boilerplate.cpp deleted file mode 100644 index 6a2ea798..00000000 --- a/core/library/tests_basis/test_boilerplate.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_boilerplate * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Puts an object through its pacess--this is intended to provide the basic * -* framework for a unit test using the hoople testing framework. * -* * -******************************************************************************* -* Copyright (c) 2011-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) - -////////////// - -class test_boilerplate : virtual public unit_base, virtual public application_shell -{ -public: - test_boilerplate() : unit_base() {} - DEFINE_CLASS_NAME("test_boilerplate"); - virtual int execute(); -}; - -HOOPLE_MAIN(test_boilerplate, ); - -////////////// - -int test_boilerplate::execute() -{ - FUNCDEF("execute"); -//do some testing - ASSERT_TRUE(true, "true is somehow not true?"); - return final_report(); -} - diff --git a/core/library/tests_basis/test_mutex.cpp b/core/library/tests_basis/test_mutex.cpp deleted file mode 100644 index 743e0004..00000000 --- a/core/library/tests_basis/test_mutex.cpp +++ /dev/null @@ -1,301 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_mutex * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace timely; -using namespace processes; -using namespace structures; -using namespace unit_test; - -//#define DEBUG_MUTEX - // uncomment for a verbose test run. - -const int MAX_MUTEX_TIMING_TEST = 2000000; - // the number of times we'll lock and unlock a mutex. - -const int DEFAULT_FISH = 32; - // the number of threads, by default. - -const int DEFAULT_RUN_TIME = 2 * SECOND_ms; - // the length of time to run the program. - -const int THREAD_PAUSE_LOWEST = 0; -const int THREAD_PAUSE_HIGHEST = 48; - // this is the range of random sleeps that a thread will take after - // performing it's actions. - -const int MIN_SAME_THREAD_LOCKING_TESTS = 100; -const int MAX_SAME_THREAD_LOCKING_TESTS = 1000; - // the range of times we'll test recursively locking the mutex. - -int concurrent_biters = 0; - // the number of threads that are currently active. - -int grab_lock = 0; - // this is upped whenever a fish obtains access to the mutex. - -mutex &guard() { static mutex _muttini; return _muttini; } - // the guard ensures that the grab lock isn't molested by two fish at - // once... hopefully. - -astring protected_string; - // this string is protected only by the mutex of guard(). - -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) - // our macro for logging with a timestamp. - -// expects guardian mutex to already be locked once when coming in. -void test_recursive_locking(chaos &_rando) -{ - int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS, - MAX_SAME_THREAD_LOCKING_TESTS); - int locked = 0; - for (int i = 0; i < test_attempts; i++) { - bool lock = !!(_rando.inclusive(0, 1)); - if (lock) { - guard().lock(); - locked++; // one more lock. - } else { - if (locked > 0) { - // must be sure we are not already locally unlocked completely. - guard().unlock(); - locked--; - } - } - } - for (int j = 0; j < locked; j++) { - // drop any locks we had left during the test. - guard().unlock(); - } -} - -//hmmm: how are these threads different so far? they seem to do exactly -// the same thing. maybe one should eat chars from the string. - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT c_testing - -class piranha : public ethread -{ -public: - chaos _rando; // our randomizer. - unit_base &c_testing; // provides for test recording. - - piranha(unit_base &testing) : ethread(0), c_testing(testing) { - FUNCDEF("constructor"); - safe_add(concurrent_biters, 1); - ASSERT_TRUE(concurrent_biters >= 1, "the piranha is very noticeable"); -//LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters)); - } - - virtual ~piranha() { - FUNCDEF("destructor"); - safe_add(concurrent_biters, -1); -//LOG("reached piranha destructor."); - } - - DEFINE_CLASS_NAME("piranha"); - - void perform_activity(void *formal(data)) { - FUNCDEF("perform_activity"); - { - // we grab the lock. - auto_synchronizer locked(guard()); - // in this case, we make use of auto-synchronizer, handily testing it as well. - ASSERT_TRUE(&locked != NIL, "auto_synchronizer should grab the mutex object's lock"); - // this is not a real test, but indicates that we did actually increase the number of - // unit tests by one, since we're using auto_synchronizer now. - safe_add(grab_lock, 1); - ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active"); - protected_string += char(_rando.inclusive('a', 'z')); - - test_recursive_locking(_rando); - - safe_add(grab_lock, -1); - } - // dropped the lock. snooze a bit. - if (!should_stop()) - time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST)); - } - -}; - -class barracuda : public ethread -{ -public: - chaos _rando; // our randomizer. - unit_base &c_testing; // provides for test recording. - - barracuda(unit_base &testing) : ethread(0), c_testing(testing) { - FUNCDEF("constructor"); - safe_add(concurrent_biters, 1); - ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed"); -//LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters)); - } - - virtual ~barracuda() { - FUNCDEF("destructor"); - safe_add(concurrent_biters, -1); -//LOG("reached barracuda destructor."); - } - - DEFINE_CLASS_NAME("barracuda"); - - void perform_activity(void *formal(data)) { - FUNCDEF("perform_activity"); - // we grab the lock. - guard().lock(); - safe_add(grab_lock, 1); - ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active"); - - test_recursive_locking(_rando); - - protected_string += char(_rando.inclusive('a', 'z')); - safe_add(grab_lock, -1); - guard().unlock(); - // done with the lock. sleep for a while. - if (!should_stop()) - time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST)); - } -}; - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*this) - -class test_mutex : virtual public unit_base, virtual public application_shell -{ -public: - chaos _rando; // our randomizer. - - test_mutex() : application_shell() {} - - DEFINE_CLASS_NAME("test_mutex"); - - int execute(); -}; - -int test_mutex::execute() -{ - FUNCDEF("execute"); - - { - // we check how long a lock and unlock of a non-locked mutex will take. - // this is important to know so that we aren't spending much of our time - // locking mutexes just due to the mechanism. - mutex ted; - time_stamp mutt_in; - for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) { - ted.lock(); - ted.unlock(); - } - time_stamp mutt_out; - double run_count = MAX_MUTEX_TIMING_TEST; - double full_run_time = (mutt_out.value() - mutt_in.value()) / SECOND_ms; - double time_per_lock = (mutt_out.value() - mutt_in.value()) / run_count; - log(a_sprintf("%.0f mutex lock & unlock pairs took %.3f seconds,", - run_count, full_run_time)); - log(a_sprintf("or %f ms per (lock+unlock).", time_per_lock)); - ASSERT_TRUE(time_per_lock < 1.0, "mutex lock timing should be super fast"); - } - - // make sure the guard is initialized before the threads run. - guard().lock(); - guard().unlock(); - - amorph thread_list; - - for (int i = 0; i < DEFAULT_FISH; i++) { - ethread *t = NIL; - if (i % 2) t = new piranha(*this); - else t = new barracuda(*this); - thread_list.append(t); - ethread *q = thread_list[thread_list.elements() - 1]; - ASSERT_EQUAL(q, t, "amorph pointer equivalence is required"); - // start the thread we added. - thread_list[thread_list.elements() - 1]->start(NIL); - } - - time_stamp when_to_leave(DEFAULT_RUN_TIME); - while (when_to_leave > time_stamp()) { - time_control::sleep_ms(100); - } - -//hmmm: try just resetting the amorph; -// that should work fine. - -#ifdef DEBUG_MUTEX - LOG("now cancelling all threads...."); -#endif - - for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel(); - -#ifdef DEBUG_MUTEX - LOG("now stopping all threads...."); -#endif - - for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop(); - - int threads_active = 0; - for (int k = 0; k < thread_list.elements(); k++) { - if (thread_list[k]->thread_active()) threads_active++; - } - ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now"); - -#ifdef DEBUG_MUTEX - LOG("resetting thread list...."); -#endif - - thread_list.reset(); // should whack all threads. - - ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now"); - -#ifdef DEBUG_MUTEX - LOG("done exiting from all threads...."); - - LOG(astring(astring::SPRINTF, "the accumulated string had %d characters " - "which means\nthere were %d thread activations from %d threads.", - protected_string.length(), protected_string.length(), - DEFAULT_FISH)); -#endif - - return final_report(); -} - -HOOPLE_MAIN(test_mutex, ) - diff --git a/core/library/tests_basis/test_string.cpp b/core/library/tests_basis/test_string.cpp deleted file mode 100644 index f89318a9..00000000 --- a/core/library/tests_basis/test_string.cpp +++ /dev/null @@ -1,1279 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_string * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//#define DEBUG_STRING - // set this to enable debugging features of the string class. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//HOOPLE_STARTUP_CODE; - -//#define DEBUG_STRING_TEST - // uncomment for testing version. - -const float TEST_RUNTIME_DEFAULT = .02 * MINUTE_ms; - // the test, by default, will run for this long. - -////////////// - -class test_string : public application_shell, public unit_base -{ -public: - test_string() {} - ~test_string() {} - - DEFINE_CLASS_NAME("test_string"); - - virtual int execute(); - - void run_test_01(); - void run_test_02(); - void run_test_03(); - void run_test_04(); - void run_test_05(); - void run_test_06(); - void run_test_07(); - void run_test_08(); - void run_test_09(); - void run_test_10(); - void run_test_11(); - void run_test_12(); - void run_test_13(); - void run_test_14(); - void run_test_15(); - void run_test_16(); - void run_test_17(); - void run_test_18(); - void run_test_19(); - void run_test_20(); - void run_test_21(); - void run_test_22(); - void run_test_23(); - void run_test_24(); - void run_test_25(); - void run_test_26(); - void run_test_27(); - void run_test_28(); - void run_test_29(); - void run_test_30(); - void run_test_31(); - void run_test_32(); - void run_test_33(); - void run_test_34(); - void run_test_35(); - void run_test_36(); - void run_test_37(); - void run_test_38(); - void run_test_39(); - void run_test_40(); - void run_test_41(); - void run_test_42(); -}; - -////////////// - -chaos rando; - -#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) - -#define WHERE __WHERE__.s() - -// test: reports an error if the condition evaluates to non-zero. -#define test(expr) { \ - ASSERT_FALSE(expr, astring("operator test should work: ") + #expr); \ -} - -static basis::astring staticity_test("yo!"); - // used below to check whether static strings are looking right. - -////////////// - -void test_string::run_test_01() -{ - FUNCDEF("run_test_01"); - -// const int TEST_EMPTY = 10000000; -// time_stamp started; -// for (int i = 0; i < TEST_EMPTY; i++) { -// astring glob = astring::empty_string(); -// } -// int duration = int(time_stamp().value() - started.value()); -// LOG(a_sprintf("duration of empty string test=%d ms", duration)); - - // test simple string operations, like construction, length, equality. - astring fred1("hyeargh!"); - astring fred2("matey."); - astring fred3; - fred3 = fred1; - fred3 += fred2; - astring fred4(fred2); - - ASSERT_EQUAL(fred1.length(), int(strlen(fred1.s())), "length should be correct (a)."); - ASSERT_EQUAL(fred2.length(), int(strlen(fred2.s())), "length should be correct (b)."); - ASSERT_EQUAL(fred3.length(), int(strlen(fred3.s())), "length should be correct (c)."); - ASSERT_EQUAL(fred4.length(), int(strlen(fred4.s())), "length should be correct (d)."); - -#ifdef DEBUG_STRING_TEST - LOG("[ " + fred1 + " & " + fred2 + "] -> " + fred3); -#endif - - ASSERT_EQUAL(fred1, astring("hyeargh!"), "failure in comparison (a)."); - ASSERT_EQUAL(fred2, astring("matey."), "failure in comparison (b)."); - ASSERT_EQUAL(fred3, astring("hyeargh!matey."), "failure in comparison (c)."); - ASSERT_EQUAL(fred4, astring("matey."), "failure in comparison (d-1)."); - ASSERT_EQUAL(fred4, fred2, "failure in comparison (d-2)."); - - a_sprintf nullo; - ASSERT_EQUAL(nullo, astring(), "forward blank a_sprintf isn't blank."); - ASSERT_EQUAL(astring(), nullo, "backward blank a_sprintf isn't blank."); - ASSERT_EQUAL(nullo, astring::empty_string(), "forward blank a_sprintf isn't empty."); - ASSERT_EQUAL(astring::empty_string(), nullo, "backward blank a_sprintf isn't empty."); -} - -void test_string::run_test_02() -{ - FUNCDEF("run_test_02"); - // assorted tests involving strings as pointers. - astring *fred1 = new astring("flipper ate"); - astring *fred2 = new astring(" my sandwich."); - astring *fred3 = new astring; - *fred3 = *fred1; - *fred3 += *fred2; - - // testing adding a null to a string. - *fred2 += (char *)NIL; - *fred3 += (char *)NIL; - -#ifdef DEBUG_STRING_TEST - LOG(astring("[ ") + *fred1 + " & " + *fred2 + "] -> " + *fred3); -#endif - - ASSERT_EQUAL(*fred1, astring("flipper ate"), "flipper A failure in comparison"); - ASSERT_EQUAL(*fred2, astring(" my sandwich."), "sandwich A failure in comparison"); - ASSERT_EQUAL(*fred3, astring("flipper ate my sandwich."), "full f-s A failure in comparison"); - delete fred1; - delete fred2; - delete fred3; -} - -void test_string::run_test_03() -{ - FUNCDEF("run_test_03"); - // tests some things about zap. - astring fleermipe("hello my frobious."); - fleermipe.zap(0, fleermipe.length() - 1); - ASSERT_EQUAL(fleermipe.length(), 0, "length not 0 after deleting entire astring"); -} - -void test_string::run_test_04() -{ - FUNCDEF("run_test_04"); - astring test_string("this test string will be chopped up."); -#ifdef DEBUG_STRING_TEST - LOG(astring("original is: ") + test_string); -#endif - astring fred(test_string.s()); - fred.zap(0, fred.find('w')); - -#ifdef DEBUG_STRING_TEST - LOG(astring("now, the one chopped through 'w' is: ") + fred); -#endif - - ASSERT_EQUAL(fred, astring("ill be chopped up."), "first zap failed"); - - astring blorg(test_string); - blorg.zap(blorg.find('p'), blorg.length() - 1); -#ifdef DEBUG_STRING_TEST - LOG(astring("now the one chopped from p to the end: ") + blorg); -#endif - - ASSERT_EQUAL(blorg, astring("this test string will be cho"), "second zap failed"); - - astring fleen; - fleen += test_string; - fleen.zap(7, 14); -#ifdef DEBUG_STRING_TEST - LOG(astring("now the one with 7 through 14 missing: ") + fleen); -#endif - - ASSERT_EQUAL(fleen, astring("this teg will be chopped up."), "third zap failed"); - -#ifdef DEBUG_STRING_TEST - LOG(astring("original astring is now: ") + test_string); -#endif - ASSERT_EQUAL(test_string, astring("this test string will be chopped up."), - "original astring was changed"); -} - -void test_string::run_test_05() -{ - FUNCDEF("run_test_05"); -#ifdef DEBUG_STRING_TEST - LOG("about to test weird things:"); -#endif - astring frieda("glorp"); - astring jorb(frieda); - astring *kleeg = new astring(jorb.s()); - astring plok = frieda; - test(frieda != jorb); - test(jorb != *kleeg); - test(*kleeg != plok); - test(plok != frieda); - astring glorp("glorp"); - test(frieda != glorp); - - WHACK(kleeg); - -#ifdef DEBUG_STRING_TEST - LOG("strings matched up okay."); -#endif - - // test new features sprintf is relying upon. - astring bubba("gumpternations"); - bubba += "_02193"; -#ifdef DEBUG_STRING_TEST - LOG(astring("bubba = ") + bubba); -#endif - ASSERT_EQUAL(bubba, astring("gumpternations_02193"), "+= on char pointer failed."); - - astring bubelah("laksos"); - bubelah += '3'; - bubelah += '8'; - bubelah += '2'; -#ifdef DEBUG_STRING_TEST - LOG(astring("bubelah = ") + bubelah); -#endif - ASSERT_EQUAL(bubelah, astring("laksos382"), "+= on char failed."); - - astring simple_spr0(astring::SPRINTF, "%% yoga splorch %%"); -#ifdef DEBUG_STRING_TEST - LOG(astring("simple sprintf 0 = ") + simple_spr0); -#endif - ASSERT_EQUAL(simple_spr0, astring("% yoga splorch %"), "simple sprintf 0 is wrong"); - astring simple_spr1(astring::SPRINTF, "%d", 23); -#ifdef DEBUG_STRING_TEST - LOG(astring("simple sprintf 1 = ") + simple_spr1); -#endif - ASSERT_EQUAL(simple_spr1, astring("23"), "simple sprintf 1 is wrong"); - astring simple_spr2(astring::SPRINTF, "%s", "yoyo"); -#ifdef DEBUG_STRING_TEST - LOG(astring("simple sprintf 2 = ") + simple_spr2); -#endif - ASSERT_EQUAL(simple_spr2, astring("yoyo"), "simple sprintf 2 is wrong"); - - astring sprintest(astring::SPRINTF, "%s has got me up the %s some %d " - "times, in %p with %d and %lu.", "marge", "ladder", 32, &kleeg, - 812377487L, 213123123L); - astring sprintest2; - sprintest2.reset(astring::SPRINTF, "%s has got me up the %s some %d " - "times, in %p with %d and %lu.", "marge", "ladder", 32, &kleeg, - 812377487L, 213123123L); - astring addr(astring::SPRINTF, "%p", &kleeg); -#ifdef DEBUG_STRING_TEST - LOG("here is your astring sir..."); - LOG(sprintest); - LOG("and addr we will see is..."); - LOG(addr); -#endif - if (sprintest != astring(astring::SPRINTF, "marge has got me up the " - "ladder some 32 times, in %s with 812377487 and 213123123.", addr.s())) - "constructed astring is wrong"; - if (sprintest2 != astring(astring::SPRINTF, "marge has got me up the " - "ladder some 32 times, in %s with 812377487 and 213123123.", addr.s())) - "reset astring is wrong"; -} - -void test_string::run_test_06() -{ - FUNCDEF("run_test_06"); - astring bungee; - bungee += "this astring"; - bungee += " has been constructed gradua"; - bungee += 'l'; - bungee += 'l'; - bungee += 'y'; - astring blorpun(astring::SPRINTF, " out of severa%c", 'l'); - bungee += blorpun; - bungee += " different bits,\nincluding"; - astring freeple(astring::SPRINTF, "%s constructed %s blarg from %f ", " this", "silly", 3.14159265358); - bungee += freeple; - bungee += "radians awa"; - bungee += 'y'; - bungee += '.'; - bungee += "\nhow does it look?\n"; -#ifdef DEBUG_STRING_TEST - LOG(bungee); -#endif - -//MessageBox(0, bungee.s(), "yo, got this", MB_ICONINFORMATION | MB_OK); - ASSERT_EQUAL(bungee, astring("this astring has been constructed gradually out of several different bits,\nincluding this constructed silly blarg from 3.141593 radians away.\nhow does it look?\n"), "constructed astring is wrong"); -} - -void test_string::run_test_07() -{ - FUNCDEF("run_test_07"); - astring a("axes"); - astring b("bakesales"); - astring x("xylophone"); - - test(a >= x); - test(a == b); - test(a > b); - test(b >= x); - test(x <= a); - test(x != x); - test(a != a); -#ifdef DEBUG_STRING_TEST - LOG("comparisons worked"); -#endif -} - -void test_string::run_test_08() -{ - FUNCDEF("run_test_08"); -#ifdef DEBUG_STRING_TEST - LOG("now testing + operator"); -#endif - astring a("fred"); - astring b(" is"); - astring c(" his"); - astring d(" name."); - astring e; - e = a + b + c + d; -#ifdef DEBUG_STRING_TEST - LOG(astring("result is: ") + e); -#endif - astring f; - f = d + c + b + a; -#ifdef DEBUG_STRING_TEST - LOG(astring("reverse is: ") + f); -#endif - astring g; - g = a + c + d + b; -#ifdef DEBUG_STRING_TEST - LOG(astring("tibetan style is: ") + g); -#endif - ASSERT_EQUAL(e, astring("fred is his name."), "astring looks wrong"); - ASSERT_EQUAL(f, astring(" name. his isfred"), "astring looks wrong"); - ASSERT_EQUAL(g, astring("fred his name. is"), "astring looks wrong"); -} - -void test_string::run_test_09() -{ - FUNCDEF("run_test_09"); - astring bleer(astring::SPRINTF, "urghla burgla #%d\n", 23); - char holder[50]; - for (int i = 0; i < bleer.length(); i++) { - bleer.stuff(holder, i); -#ifdef DEBUG_STRING_TEST - LOG(astring(astring::SPRINTF, "%d", i) + " has: " + holder); -#endif - astring my_copy(bleer); - my_copy.zap(i, bleer.length() - 1); - ASSERT_EQUAL(my_copy, astring(holder), "should see no inaccurate stuff() call"); - } -} - -void test_string::run_test_10() -{ - FUNCDEF("run_test_10"); -#ifdef DEBUG_STRING_TEST - LOG("The tenth ones:"); -#endif - astring george("this one will be mangled."); - ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (q)."); - astring tmp1(george.substring(1, 7)); // constructor. - astring tmp2 = george.substring(10, george.length() - 1); // constructor. -#ifdef DEBUG_STRING_TEST - LOG(tmp1 + "... " + tmp2); -#endif - ASSERT_INEQUAL(tmp1, tmp2, "bizarre equality occurred!"); - ASSERT_FALSE( (tmp1 > tmp2) || (tmp2 < tmp1), "bizarre comparison error."); - ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (z)."); -#ifdef DEBUG_STRING_TEST - LOG(george.substring(1, 7)); - LOG("... "); - LOG(george.substring(10, george.length() - 1)); -#endif - ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (a)."); - george.insert(14, "terribly "); - ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (b)."); -#ifdef DEBUG_STRING_TEST - LOG(george); -#endif - astring chunky; - astring mssr_boef_la_tet("eeyoy eye eye capn"); - mssr_boef_la_tet.substring(chunky, 2, 7); - ASSERT_EQUAL(chunky, astring("yoy ey"), "contents wrong after substring"); - - astring fred(george); - ASSERT_TRUE(george.compare(fred, 0, 0, george.length() - 1, true), "did not work"); - ASSERT_TRUE(george.compare(fred, 8, 8, george.length() - 1 - 8, true), "partial did not work"); - - astring taco1("iLikeTacosNSuch"); - astring taco2("iLikeTaCosNSuch"); - ASSERT_TRUE(taco1.compare(taco2, 0, 0, taco1.length() - 1, false), - "tacos case-insensitive compare A did not work"); - ASSERT_FALSE(taco1.compare(taco2, 0, 0, taco1.length() - 1, true), - "tacos case-sensitive compare B did not work"); - - ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (c)."); - george.zap(14, 22); -#ifdef DEBUG_STRING_TEST - LOG(george); -#endif - astring fred_part; - fred_part = fred.substring(0, 13); - ASSERT_EQUAL(fred_part.length(), int(strlen(fred_part.s())), "length incorrect (d)."); - ASSERT_TRUE(george.compare(fred_part, 0, 0, 13, true), "did not work"); - fred_part = fred.substring(23, fred.length() - 1); - ASSERT_EQUAL(fred_part.length(), int(strlen(fred_part.s())), "length incorrect (e)."); - ASSERT_TRUE(george.compare(fred_part, 14, 0, fred_part.length() - 1, true), "did not work"); -#ifdef DEBUG_STRING_TEST - LOG("compares okay"); -#endif -} - -void test_string::run_test_11() -{ - FUNCDEF("run_test_11"); - astring empty; - ASSERT_FALSE(empty.length(), "empty string judged not"); - ASSERT_TRUE(!empty, "not on empty string doesn't work"); - astring non_empty("grungee"); - ASSERT_TRUE(non_empty.length(), "non-empty string judged empty"); - ASSERT_FALSE(!non_empty, "not on non-empty string doesn't work"); -} - -void test_string::run_test_12() -{ - FUNCDEF("run_test_12"); - astring elf0(astring::SPRINTF, "%%_%%_%%"); - ASSERT_FALSE(strcmp(elf0.s(), "%_%_%"), "failed %% printing"); - - char fred[6] = { 'h', 'y', 'a', 'r', 'g', '\0' }; - astring fred_copy(astring::UNTERMINATED, fred, 5); - ASSERT_EQUAL(fred_copy.length(), 5, "length of copy is wrong"); - ASSERT_EQUAL(fred_copy, astring("hyarg"), "length of copy is wrong"); - - char hugh[6] = { 'o', 'y', 'o', 'b', 'o', 'y' }; - astring hugh_copy(astring::UNTERMINATED, hugh, 3); - ASSERT_EQUAL(hugh_copy.length(), 3, "length of copy is wrong"); - ASSERT_EQUAL(hugh_copy, astring("oyo"), "length of copy is wrong"); - - astring another_copy; - another_copy.reset(astring::UNTERMINATED, fred, strlen(fred)); - ASSERT_EQUAL(another_copy.length(), 5, "length of reset copy is wrong"); - ASSERT_EQUAL(another_copy, astring("hyarg"), "length of reset copy is wrong"); -} - -void test_string::run_test_13() -{ -// FUNCDEF("run_test_13"); - // check for possible memory leaks in these combined ops.... 13th. - const astring churg("borjh sjh oiweoklj"); - astring pud = churg; - astring flug = "kase iqk aksjir kljasdo"; - const char *nerf = "ausd qwoeui sof zjh qwei"; - astring snorp = nerf; - pud = "snugbo"; - snorp = flug; - astring pundit("murph"); - pundit = nerf; -} - -void test_string::run_test_14() -{ - FUNCDEF("run_test_14"); - // test the new dynamic sprintf'ing for long strings... 14th. - const int longish_size = 5000; - char temp[longish_size]; - for (int i = 0; i < longish_size; i++) - temp[i] = char(rando.inclusive(1, 255)); - temp[longish_size - 1] = '\0'; - a_sprintf longish("this is a really darned long string of stuff: %s,\nbut doesn't it look interesting?", temp); - // still with us? - longish.zap(longish.length() / 3, 2 * longish.length() / 3); - longish += longish; - ASSERT_EQUAL(longish.length(), int(strlen(longish.s())), "length is incorrect."); -} - -void test_string::run_test_15() -{ - FUNCDEF("run_test_15"); - // test the new dynamic sprintf'ing for long strings... 15th. - int try_1 = 32767; - astring testy(astring::SPRINTF, "%d", try_1); - ASSERT_INEQUAL(testy.convert(95), 95, "default value returned, so it failed."); - ASSERT_EQUAL(testy.convert(95), try_1, "correct value was not returned."); - - long try_2 = 2938754L; - testy = astring(astring::SPRINTF, "%ld", try_2); - ASSERT_INEQUAL((double)testy.convert(98L), (double)98L, "default value returned, so it failed."); - ASSERT_EQUAL((double)testy.convert(98L), (double)try_2, "correct value was not returned."); - - testy = astring(astring::SPRINTF, "%ld", try_2); - ASSERT_INEQUAL((double)testy.convert(98L), (double)98L, "default value returned, so it failed."); - ASSERT_EQUAL((double)testy.convert(98L), (double)try_2, "correct value was not returned."); - - float try_3 = float(2938.754); // weird msvcpp error if don't cast. - testy = astring(astring::SPRINTF, "%f", try_3); - float found = testy.convert(float(98.6)); - ASSERT_INEQUAL(found, 98.6, "default value returned, so it failed."); - ASSERT_EQUAL(found, try_3, "correct value was not returned."); - - { - double try_4 = 25598437.712385; - testy = astring(astring::SPRINTF, "%f", try_4); - double found2 = testy.convert(double(98.6)); - ASSERT_INEQUAL(found2, 98.6, "default value returned, so it failed."); - ASSERT_EQUAL(found2, try_4, "correct value was not returned."); - } - - { - double try_4 = 25598437.712385e38; - testy = astring(astring::SPRINTF, "%f", try_4); - double found2 = testy.convert(double(98.6)); - ASSERT_INEQUAL(found2, 98.6, "default value returned, so it failed."); - ASSERT_EQUAL(found2, try_4, "correct value was not returned."); - } -} - -void test_string::run_test_16() -{ - FUNCDEF("run_test_16"); - // the 16th test group tests boundary checking. - astring gorf("abc"); - for (int i = -3; i < 25; i++) gorf[i] = 't'; - ASSERT_EQUAL(gorf, astring("ttt"), "correct value was not returned (a)."); - ASSERT_EQUAL(gorf.length(), 3, "length is incorrect (a)."); - gorf.insert(3, astring("bru")); - ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (b)."); - ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (b)."); - gorf.insert(35, astring("snu")); - ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (c)."); - ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (c)."); - gorf.insert(-30, astring("eep")); - ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (d)."); - ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (d)."); -} - -void test_string::run_test_17() -{ -// FUNCDEF("run_test_17"); - // 17th test checks construction of temporaries. -/* this test set causes the obnoxious 16 bit codeguard error from hell, as - does use of temporary objects in ostream << operators. argh! */ - astring("jubjo"); - astring("obo"); - astring("flrrpominort").substring(3, 6); -} - -void test_string::run_test_18() -{ -#ifdef DEBUG_STRING_TEST - FUNCDEF("run_test_18"); -#endif - // 18th test group checks windows related functions. -#ifdef AFXDLL - AfxSetResourceHandle(GET_INSTANCE_HANDLE()); - // required for mfc to see the proper instance handle. - - // this tests the "constructor from resource". - astring libname = rc_string(IDS_BASIS_NAME); - ASSERT_EQUAL(libname, astring("Basis Library"), - astring("library name is a mismatch: comes out as \"") + libname + "\"."); - #define IDS_SOME_BAD_UNKNOWN_STRING_HANDLE 30802 - astring bogus_name = rc_string(IDS_SOME_BAD_UNKNOWN_STRING_HANDLE); - ASSERT_FALSE(bogus_name.length(), "bogus rc string had length"); - - // tests conversions from CString to astring and back. - astring george("yo yo yo"); - CString hal = convert(george); - astring borgia = convert(hal); - -#ifdef DEBUG_STRING_TEST - LOG(astring("cstringy conversions: ") + george); - LOG((const char *)hal); - LOG(borgia); -#endif - - ASSERT_EQUAL(borgia, george, "got the wrong value"); -#endif -} - -void test_string::run_test_19() -{ - FUNCDEF("run_test_19"); - // 19th test group is devoted to anthony wenzel's % printing bug. - astring problematic_example(astring::SPRINTF, "this should have %d% more " - "stuffing than before!", 20); -//MessageBox(0, astring("got a string of \"%s!\"", problematic_example.s()).s(), "yo!", MB_OK); - ASSERT_EQUAL(problematic_example, astring("this should have 20% more stuffing than before!"), "failure to print correct phrase"); -} - -void test_string::run_test_20() -{ -#ifdef DEBUG_STRING_TEST - FUNCDEF("run_test_20"); -#endif - // 20th test group is devoted to another wenzelbug. - - // Hey, I just found out (in an ugly way) that the following doesn't work: - char myText[] = "OK"; - astring myString(astring::SPRINTF, "%04s", myText); -#ifdef DEBUG_STRING_TEST - LOG(astring("first try gets: ") + myString); -#endif - - char myText8[] = "OK"; - char my_text_4[90]; - sprintf(my_text_4, "%4s", myText8); -#ifdef DEBUG_STRING_TEST - LOG(astring("second try gets: ") + astring(my_text_4)); -#endif - - // Looks like you don't handle the "%04s" arguments properly. I can make - // it work as follows: - char myText2[] = "OK"; - char myText3[50]; - sprintf(myText3, "%4s", myText2); - astring myString2(myText3); -#ifdef DEBUG_STRING_TEST - LOG(astring("third try gets: ") + myString2); -#endif -} - -void test_string::run_test_21() -{ - FUNCDEF("run_test_21"); - // 21st test group checks out the strip spaces and replace functions. - astring spacey(" dufunk "); - astring temp = spacey; - temp.strip_spaces(astring::FROM_FRONT); - ASSERT_EQUAL(temp, astring("dufunk "), "created wrong string"); - temp = spacey; - temp.strip_spaces(astring::FROM_END); - ASSERT_EQUAL(temp, astring(" dufunk"), "created wrong string"); - temp = spacey; - temp.strip_spaces(astring::FROM_BOTH_SIDES); - ASSERT_EQUAL(temp, astring("dufunk"), "created wrong string"); - - astring placemat("mary had a red hooded cobra she kept around her neck " - "and it hissed at the people as they walked by her tent."); - ASSERT_TRUE(placemat.replace("had", "bought"), "replace failed"); - ASSERT_TRUE(!placemat.replace("hoded", "bought"), "replace didn't fail but should have"); - ASSERT_TRUE(placemat.replace("she", "funkateria"), "replace failed"); - ASSERT_TRUE(placemat.replace("hooded", "hood"), "replace failed"); - ASSERT_TRUE(placemat.replace("cobra", "in the"), "replace failed"); - - int indy = placemat.find("kept"); - ASSERT_FALSE(negative(indy), "couldn't find string"); - placemat[indy - 1] = '.'; - placemat.zap(indy, placemat.end()); - ASSERT_EQUAL(placemat, astring("mary bought a red hood in the funkateria."), "got wrong result string"); -} - -void test_string::run_test_22() -{ - FUNCDEF("run_test_22"); - // 22nd test: morgan's find bug. - astring B("buzz*buzz*"); - { - int x = B.find('*'); // correctly finds the first *. - ASSERT_EQUAL(x, 4, "got wrong index for first"); - x++; - x = B.find('*', x); // correctly finds the second *. - ASSERT_EQUAL(x, 9, "got wrong index for second"); - x++; // now x == B.length(). - x = B.find('*', x); - // error was: finds the second * again (and again and again and - // again...). At this point it should return OUT_OF_RANGE. - ASSERT_FALSE(x > 0, "got wrong outcome for third"); - } - { - int x = B.find("z*"); // correctly finds the first z*. - ASSERT_EQUAL(x, 3, "got wrong index for fourth"); - x++; - x = B.find("z*", x); // correctly finds the second *. - ASSERT_EQUAL(x, 8, "got wrong index for fifth"); - x++; // now x == B.length(). - x = B.find("z*", x); - // error was: finds the second * again (and again and again and - // again...). At this point it should return OUT_OF_RANGE. - ASSERT_FALSE(x > 0, "got wrong outcome for sixth"); - } -} - -void test_string::run_test_23() -{ - FUNCDEF("run_test_23"); - // 23rd test: test the new strip function. - astring strip_string("!@#$%^&*()"); - - astring no_stripper("this shouldn't change"); - no_stripper.strip(strip_string, astring::FROM_BOTH_SIDES); - ASSERT_EQUAL(no_stripper, astring("this shouldn't change"), "first test failed comparison"); - - astring strippee_orig("&$(*(@&@*()()!()@*(@(*fudge#((@(*@**#)(@#)(#"); - astring strippee(strippee_orig); - strippee.strip(strip_string, astring::FROM_BOTH_SIDES); - ASSERT_EQUAL(strippee, astring("fudge"), "second test failed comparison"); - - strippee = strippee_orig; - strippee.strip(strip_string, astring::FROM_FRONT); - ASSERT_EQUAL(strippee, astring("fudge#((@(*@**#)(@#)(#"), "third test failed comparison"); - - strippee = strippee_orig; - strippee.strip(strip_string, astring::FROM_END); - ASSERT_EQUAL(strippee, astring("&$(*(@&@*()()!()@*(@(*fudge"), "fourth test failed comparison"); -} - -void test_string::run_test_24() -{ - FUNCDEF("run_test_24"); -#ifdef __WIN32__ - // 24th test group tests _bstr_t conversions. - _bstr_t beast("abcdefgh"); -#ifdef DEBUG_STRING_TEST - LOG(astring("the beast is ") + beast.operator char *() + - astring(astring::SPRINTF, " with length %d", beast.length())); -#endif - astring convert = beast; -#ifdef DEBUG_STRING_TEST - LOG(astring("the convert is ") + convert - + astring(astring::SPRINTF, " with length %d", convert.length())); -#endif - ASSERT_EQUAL(convert, astring("abcdefgh"), "first test failed comparison"); - - astring jethro("i want a hog sandwich"); - _bstr_t pork = string_convert::to_bstr_t(jethro); - ASSERT_FALSE(strcmp(pork.operator char *(), jethro.s()), "second test failed comparison"); -#endif -} - -void test_string::run_test_25() -{ - FUNCDEF("run_test_25"); - // 25th test group tests simple comparisons. - astring fred = "asdoiuaoisud"; - ASSERT_INEQUAL(fred, astring(), "first test failed comparison"); - astring bub = fred; - ASSERT_EQUAL(fred, bub, "second test failed comparison"); - fred = ""; - ASSERT_EQUAL(fred, astring(), "third test failed comparison"); - ASSERT_FALSE(fred.length(), "fourth test failed comparison"); -} - -void test_string::run_test_26() -{ -// FUNCDEF("run_test_26"); - // 26th test group does simple time_stamp::notarize operations. these are more for - // ensuring boundschecker gets to see some of this. - astring t2 = time_stamp::notarize(false); - astring t4 = time_stamp::notarize(true); -} - -void test_string::run_test_27() -{ -// FUNCDEF("run_test_27"); - // 27th test group plays around with idate in an attempt to get - // boundschecker to complain. - timely::day_in_year d1 = date_now(); - timely::clock_time t1 = time_now(); - timely::time_locus dt1 = now(); - astring sd1 = d1.text_form(); - astring st1 = t1.text_form(); - astring sdt1 = dt1.text_form_long(); -} - -void test_string::run_test_28() -{ - FUNCDEF("run_test_28"); - // 28th test group does sprintfs on shorts and such. - basis::un_int in1 = 27; - basis::un_short in2 = 39; - char in3 = 'Q'; - astring testy(astring::SPRINTF, "%u:%hu:%c", in1, in2, in3); - ASSERT_EQUAL(testy, astring("27:39:Q"), "fourth test failed comparison"); -} - -void test_string::run_test_29() -{ - FUNCDEF("run_test_29"); - // 29th test group tries out the packing support. - astring a("would an onion smell so sweet?"); - byte_array p1; - a.pack(p1); - astring b; - ASSERT_TRUE(b.unpack(p1), "first unpack failed"); - ASSERT_EQUAL(b, a, "first comparison failed"); - a = "128 salamanders cannot be wrong."; - a.pack(p1); - ASSERT_TRUE(b.unpack(p1), "second unpack failed"); - ASSERT_EQUAL(b, a, "second comparison failed"); -} - -void standard_sprintf_test(const char *parm_string) -{ -// FUNCDEF("standard_sprintf_test"); - astring print_into(' ', 20000); - print_into[0] = '\0'; -//check these!!!: - int i1 = int(rando.inclusive(0, 23945)); - long l1 = long(rando.inclusive(-2394, 2998238)); - un_int u1 = basis::un_int(rando.inclusive(0, 124395)); - un_short s1 = basis::un_short(rando.inclusive(0, 65535)); - sprintf(print_into.s(), "%d %ld %u %hu %s", i1, l1, u1, s1, parm_string); - sprintf(print_into.s(), "%c %d %c %s %s %lu", char(rando.inclusive('a', 'z')), - int(rando.inclusive(0, 23945)), char(rando.inclusive('A', 'Z')), - parm_string, parm_string, basis::un_long(rando.inclusive(0, 2998238))); -} - -void test_string::run_test_30() -{ - // 30th test group checks astring sprintf. - FUNCDEF("run_test_30"); - astring parm_string = string_manipulation::make_random_name(40, 140); - astring print_into(' ', 20000); - print_into[0] = '\0'; - int i1 = int(rando.inclusive(0, 23945)); - long l1 = long(rando.inclusive(-2394, 2998238)); - un_int u1 = basis::un_int(rando.inclusive(0, 124395)); - un_short s1 = basis::un_short(rando.inclusive(0, 65535)); - char test_same[20010]; - sprintf(test_same, "%d %ld %u %hu %s", i1, l1, u1, s1, parm_string.s()); - print_into.sprintf("%d %ld %u %hu %s", i1, l1, u1, s1, parm_string.s()); - ASSERT_EQUAL(astring(test_same), print_into, "sprintf should get same results as standard"); -//do test for this one too. - print_into.sprintf("%c %d %c %s %s %lu", char(rando.inclusive('a', 'z')), - int(rando.inclusive(0, 23945)), char(rando.inclusive('A', 'Z')), - parm_string.observe(), parm_string.s(), basis::un_long(rando.inclusive(0, 2998238))); -} - -void test_string::run_test_31() -{ - FUNCDEF("run_test_31"); - // testing of character repeat constructor. - astring dashes('-', 20); - astring non_plusses('+', 0); - astring plusses('+', 1); - ASSERT_EQUAL(dashes.length(), 20, astring("dashes has wrong length!")); - ASSERT_EQUAL(dashes, astring("--------------------"), astring("dashes has wrong contents! '") + dashes + "' vs. '" + astring("--------------------'")); - ASSERT_FALSE(non_plusses.length(), astring("non_plusses has wrong length!")); - ASSERT_EQUAL(plusses.length(), 1, astring("plusses has wrong length!")); - ASSERT_EQUAL(plusses, astring("+"), astring("plusses has wrong contents!")); - ASSERT_EQUAL(plusses, astring('+', 1), astring("plusses second compare failed!")); -} - -void test_string::run_test_32() -{ - FUNCDEF("run_test_32"); - // tests creating a huge string and ripping it apart. - - const int CHUNK_SIZE = 2 * MEGABYTE; - // the block factor for our string. we expect not to go above this during - // the testing. - const int MIN_ADDITION = 10000; const int MAX_ADDITION = 80000; - // these are the largest chunk we add to a string at a time. - const int BUILD_AND_BURN_ITERATIONS = 1; - // number of times to test building up and tearing down. - - // the string we build up and tear down. - astring slab; - -//hmmm: maybe have a mixed phase where tearing and adding -// happens frequently and interspersed. - - for (int iters = 0; iters < BUILD_AND_BURN_ITERATIONS; iters++) { - - int size = 0; // independent count of expected size. - // we don't want to bother allocating the big chunk more than once, so - // we'll add to the string until it's within range of going over. - while (slab.length() < CHUNK_SIZE - MAX_ADDITION - 20) { -//make this into add_a_chunk - astring addition = string_manipulation::make_random_name(MIN_ADDITION, - MAX_ADDITION); - slab += addition; - size += addition.length(); - ASSERT_EQUAL(size, slab.length(), astring("size is incorrect after add!")); - } - - // now we eat it up. - while (slab.length()) { -//make this into zap_a_chunk - int zap_start = rando.inclusive(0, slab.end()); - int zap_end = rando.inclusive(0, slab.end()); - flip_increasing(zap_start, zap_end); - int range_length = zap_end - zap_start + 1; - ASSERT_FALSE(negative(range_length), astring("flip_increasing failed!")); - slab.zap(zap_start, zap_end); - size -= range_length; - ASSERT_EQUAL(size, slab.length(), astring("size is incorrect after zap!")); - } - } -} - -void test_string::run_test_33() -{ - FUNCDEF("run_test_33"); - // 33rd test group exercises the replace functions. - { - astring to_modify("\\\\feeby\\path\\yo"); - ASSERT_TRUE(to_modify.replace("\\", "/"), "failed to replace the string"); - ASSERT_EQUAL(to_modify, astring("/\\feeby\\path\\yo"), "produced wrong resultant string"); - while (to_modify.replace("\\", "/")) {} - ASSERT_EQUAL(to_modify, astring("//feeby/path/yo"), "produced wrong final string"); - } - { - astring to_modify("\\superduper\\dynamo\\looper"); - ASSERT_TRUE(to_modify.replace("\\", "/"), "failed to replace the string"); - ASSERT_EQUAL(to_modify, astring("/superduper\\dynamo\\looper"), "produced wrong resultant string"); - while (to_modify.replace("\\", "/")) {} - ASSERT_EQUAL(to_modify, astring("/superduper/dynamo/looper"), "produced wrong final string"); - } - { - astring id = "/SRV=1/SRC=1"; - astring id1 = id; - while (id1.replace("/", " ")) {} -// LOG(astring("replacing / with ' ' in first test (was ") + id + -// ") gives " + id1); - ASSERT_EQUAL(id1, astring(" SRV=1 SRC=1"), "produced wrong final string"); - - astring id2 = id; - while (id2.replace("=", ":")) {} -// LOG(astring("replacing = with : in second test (was ") + id + -// ") gives " + id2); - ASSERT_EQUAL(id2, astring("/SRV:1/SRC:1"), "produced wrong final string"); - } -} - -void test_string::run_test_34() -{ -// FUNCDEF("run_test_34"); - -//not in use right now. - -} - -void test_string::run_test_35() -{ - FUNCDEF("run_test_35"); - // test the shrink method. - astring termo('R', 2812); - ASSERT_EQUAL(termo.length(), 2812, "length should be as requested"); - termo[1008] = '\0'; - termo.shrink(); - ASSERT_EQUAL(termo.get_implementation().internal_real_length(), 1010, a_sprintf("failure in shrunken size: " "wanted 1010 and got %d.", termo.get_implementation().internal_real_length())); - astring termo2('R', 1008); - ASSERT_EQUAL(termo, termo2, "wrong value produced"); -} - -void test_string::run_test_36() -{ - FUNCDEF("run_test_36"); - // test the text form on a string array (which is mildly related to strings; - // this could be moved to a common templates test someday). - string_array torpid; - torpid += "york"; - torpid += "burger"; - torpid += "petunia"; - torpid += "dumptruck"; - ASSERT_EQUAL(torpid.text_form(), astring("\"york\",\"burger\",\"petunia\",\"dumptruck\""), "wrong value computed"); - string_array sacral; - sacral += "gumboat"; - ASSERT_EQUAL(sacral.text_form(), astring("\"gumboat\""), "wrong value computed"); - - string_array paknid; - paknid += "gorboochy"; - paknid += "rangolent"; - byte_array packed; - structures::pack_array(packed, paknid); - - string_array upnort; - ASSERT_TRUE(structures::unpack_array(packed, upnort), "failed to unpack"); - ASSERT_FALSE(packed.length(), "array still has bytes!"); - - string_array stongent; - stongent += "pemulack"; - stongent += "bluzzent"; - stongent += "crupto"; - stongent += "floonack"; - stongent += "atoona"; - packed.reset(); - structures::pack_array(packed, stongent); - - string_array belzorp; - ASSERT_TRUE(structures::unpack_array(packed, belzorp), "failed to unpack"); - ASSERT_FALSE(packed.length(), "array still has bytes!"); -} - -void test_string::run_test_37() -{ - FUNCDEF("run_test_37"); - // 37th test group used to try out the old packing support, but now is - // just the same as test 29. it would be good to make this different. - astring a("would an onion smell so sweet?"); - byte_array p1; - a.pack(p1); - astring b; - ASSERT_TRUE(b.unpack(p1), "first unpack failed"); - ASSERT_EQUAL(b, a, "first comparison failed"); - a = "128 salamanders cannot be wrong."; - a.pack(p1); - ASSERT_TRUE(b.unpack(p1), "second unpack failed"); - ASSERT_EQUAL(b, a, "second comparison failed"); -} - -void test_string::run_test_38() -{ -// FUNCDEF("run_test_38"); - double to_print = 2.345; - a_sprintf non_deadly("%.1f", to_print); -/// LOG(astring("printed: ") + non_deadly); - - to_print = 1.797E+254; - // this value breaks things. - - char bucket[2000]; - bucket[0] = '\0'; - sprintf(bucket, "%.1f", to_print); -/// LOG(astring("sprintf printed: ") + bucket); - - a_sprintf deadly("%.1f", to_print); -/// LOG(astring("printed: ") + deadly); -} - -void test_string::run_test_39() -{ - FUNCDEF("run_test_39"); - const char *find_set = "!?.;"; - astring test_1 = "how do i get to balthazar square? it stinks!"; - ASSERT_EQUAL(test_1.find_any(find_set), 32, "first find returned wrong result"); - ASSERT_EQUAL(test_1.find_any(find_set, 33), 44, "second find returned wrong result"); - ASSERT_EQUAL(test_1.find_any(find_set, 40, true), 32, "third find returned wrong result"); -} - -void test_string::run_test_40() -{ - FUNCDEF("run_test_40"); - int test_num = 1; - #define test_name() a_sprintf("test %d: ", test_num) - { - astring target = "xabab"; - astring from = "ab"; - astring to = "dc"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("xdcdc"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "xabab"; - astring from = "ab"; - astring to = "ab"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("xabab"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "xabab"; - astring from = "ab"; - astring to = "a"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("xaa"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "ababx"; - astring from = "ab"; - astring to = "a"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("aax"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "suzzle rumpetzzes gnargle rezztor"; - astring from = "zz"; - astring to = "zzz"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("suzzzle rumpetzzzes gnargle rezzztor"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "qqqq"; - astring from = "q"; - astring to = "qqq"; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("qqqqqqqqqqqq"), test_name() + "didn't replace properly"); - test_num++; - } - { - astring target = "glorg snorp pendle funk"; - astring from = " "; - astring to = ""; - ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); - ASSERT_EQUAL(target, astring("glorgsnorppendlefunk"), test_name() + "didn't replace properly"); - test_num++; - } -} - -void test_string::run_test_41() -{ - FUNCDEF("run_test_41"); - int test_num = 0; - #define test_name() a_sprintf("test %d: ", test_num) - { - test_num++; - astring target = "xabab"; - const char *finding1 = "ab"; - ASSERT_EQUAL(target.find_non_match(finding1, 0, false), 0, test_name() + "didn't find right location A"); - const char *finding2 = "xb"; - ASSERT_EQUAL(target.find_non_match(finding2, target.length() - 1, true), 3, test_name() + "didn't find right location B"); - const char *finding3 = "c"; - ASSERT_EQUAL(target.find_non_match(finding3, 0, false), 0, test_name() + "wrong answer for test C"); - ASSERT_EQUAL(target.find_non_match(finding3, target.length() - 1, true), target.length() - 1, - test_name() + "wrong answer for test D"); - } - { - test_num++; - astring target = "abcadefghoota"; - const char *finding1 = "bcdfghot"; - ASSERT_EQUAL(target.find_non_match(finding1, 0, false), 0, test_name() + "didn't find right location A"); - ASSERT_EQUAL(target.find_non_match(finding1, 1, false), 3, test_name() + "didn't find right location B"); - ASSERT_EQUAL(target.find_non_match(finding1, target.length() - 1, true), target.length() - 1, test_name() + "didn't find right location C"); - ASSERT_EQUAL(target.find_non_match(finding1, target.length() - 2, true), 5, test_name() + "didn't find right location D"); - ASSERT_EQUAL(target.find_non_match(finding1, 3, false), 3, test_name() + "didn't find right location E"); - ASSERT_EQUAL(target.find_non_match(finding1, 4, false), 5, test_name() + "didn't find right location F"); - - } - -} - -// exercise the middle, right and left methods. -void test_string::run_test_42() -{ - FUNCDEF("run_test_42"); - - astring hobnob("all the best robots are bending robots"); - - ASSERT_EQUAL(hobnob.middle(5, 7), astring("he best"), "failed to find middle of string"); - ASSERT_EQUAL(hobnob.right(10), astring("ing robots"), "failed to find right side of string"); - ASSERT_EQUAL(hobnob.left(6), astring("all th"), "failed to find right side of string"); -} - -////////////// - -int test_string::execute() -{ - FUNCDEF("execute"); - -// ASSERT_EQUAL(0, 1, "fake assertion to test jenkins log parsing"); - ASSERT_EQUAL(1, 1, "non-fake assertion to test jenkins log parsing"); - - ASSERT_EQUAL(staticity_test, astring("yo!"), "wrong contents"); - - time_stamp end_time(TEST_RUNTIME_DEFAULT); - // when we're done testing. - - int i = 0; // iteration counter. - while (time_stamp() < end_time) { - // we run the test until our time runs out. - i++; // next iteration. -#ifdef DEBUG_STRING_TEST - LOG(astring(astring::SPRINTF, "index %d", i)); -#endif - - // beginning of test sets. - run_test_01(); - run_test_02(); - run_test_03(); - run_test_04(); - run_test_05(); - run_test_06(); - run_test_07(); - run_test_08(); - run_test_09(); - run_test_10(); - run_test_11(); - run_test_12(); - run_test_13(); - run_test_14(); - run_test_15(); - run_test_16(); - run_test_17(); - run_test_18(); - run_test_19(); - run_test_20(); - run_test_21(); - run_test_22(); - run_test_23(); - run_test_24(); - run_test_25(); - run_test_26(); - run_test_27(); - run_test_28(); - run_test_29(); -//too slow run_test_30(); - run_test_31(); - run_test_32(); - run_test_33(); -//retired run_test_34(); - run_test_35(); - run_test_36(); - run_test_37(); - run_test_38(); - run_test_39(); - run_test_40(); - run_test_41(); - run_test_42(); - } - - return final_report(); -} - -HOOPLE_MAIN(test_string, ) - - diff --git a/core/library/tests_basis/test_system_preconditions.cpp b/core/library/tests_basis/test_system_preconditions.cpp deleted file mode 100644 index f18f17d6..00000000 --- a/core/library/tests_basis/test_system_preconditions.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* -* Name : test_system_preconditions -* Author : Chris Koeritz -** -* Copyright (c) 1993-$now By Author. 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 2 of -* the License or (at your option) any later version. This is online at: -* http://www.fsf.org/copyleft/gpl.html -* Please send any updates to: fred@gruntose.com -*/ - -#include "checkup.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace system_checkup; -using namespace structures; -using namespace unit_test; - -class test_system_preconditions : virtual public unit_base, virtual public application_shell -{ -public: - test_system_preconditions() : application_shell() {} - DEFINE_CLASS_NAME("test_system_preconditions"); - virtual int execute(); -}; - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT c_parent - -class burpee -{ -public: - burpee(unit_base &parent) : c_parent(parent), my_string(new astring) { *my_string = "balrog"; } - DEFINE_CLASS_NAME("burpee"); - virtual ~burpee() { - FUNCDEF("destructor"); - WHACK(my_string); - ASSERT_FALSE(my_string, "whack test should not fail to clear string"); - } - -protected: - unit_base &c_parent; - -private: - astring *my_string; -}; - -#undef UNIT_BASE_THIS_OBJECT - -////////////// - -#define UNIT_BASE_THIS_OBJECT c_parent - -class florba : public burpee -{ -public: - florba(unit_base &parent) : burpee(parent), second_string(new astring) - { *second_string = "loquacious"; } - DEFINE_CLASS_NAME("florba"); - virtual ~florba() { - FUNCDEF("destructor"); - WHACK(second_string); - ASSERT_FALSE(second_string, "whack test should clear string in derived class"); - } - -private: - astring *second_string; -}; - -#undef UNIT_BASE_THIS_OBJECT - -////////////// - -// back to default now. -#define UNIT_BASE_THIS_OBJECT (*this) - -struct testing_file_struct : public FILE {}; - -// NOTE: an important part of this test program is running it under something -// like boundschecker to ensure that there are no memory leaks caused by -// invoking WHACK. apparently diab 3 is unable to implement WHACK correctly. - -int test_system_preconditions::execute() -{ - FUNCDEF("execute") - // let's see what this system is called. - log(astring("The name of this software system is: ") - + application_configuration::software_product_name()); - ASSERT_TRUE(strlen(application_configuration::software_product_name()), - "product should not be blank"); - - // and what this program is called. - log(astring("The application is called: ") + application_configuration::application_name()); - ASSERT_TRUE(application_configuration::application_name().length(), - "application name should not be blank"); - - // testing compiler's ansi c++ compliance. - for (int q = 0; q < 198; q++) { - int treno = q; - int malfoy = treno * 3; -// log(a_sprintf("%d", malfoy)); - } - // this should not be an error. the scope of q should be within the loop and - // not outside of it. - int q = 24; - ASSERT_FALSE(q > 190, "no weirdness should happen with compiler scoping"); - - // test that the WHACK function operates properly. - burpee *chunko = new burpee(*this); - florba *lorkas = new florba(*this); - burpee *alias = lorkas; - - WHACK(chunko); - WHACK(alias); - ASSERT_FALSE(chunko, "chunko whack test should succeed"); - ASSERT_FALSE(alias, "aliased lorkas whack test should succeed"); - ASSERT_TRUE(lorkas, "original lorkas should not have been cleared"); - lorkas = NIL; - - ASSERT_EQUAL((int)sizeof(testing_file_struct), (int)sizeof(FILE), - "struct size test, sizeof testing_file_struct and sizeof FILE should not differ"); - - // now do the crucial tests on the OS, platform, compiler, etc. - ASSERT_TRUE(check_system_characteristics(*this), - "required system characteristics should be found"); - -#ifdef __WIN32__ - known_operating_systems os = determine_OS(); - if (os == WIN_95) - printf("This is windows 95.\n"); - else if (os == WIN_NT) - printf("This is windows NT.\n"); - else if (os == WIN_2K) - printf("This is windows 2000.\n"); - else if (os == WIN_XP) - printf("This is windows XP.\n"); - else - printf("This OS is unknown.\n"); -#endif - - version os_ver = application_configuration::get_OS_version(); - printf("OS version: %s\n", os_ver.text_form().s()); - - return final_report(); -} - -HOOPLE_MAIN(test_system_preconditions, ) - diff --git a/core/library/tests_configuration/makefile b/core/library/tests_configuration/makefile deleted file mode 100644 index 04995769..00000000 --- a/core/library/tests_configuration/makefile +++ /dev/null @@ -1,10 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_configuration -TYPE = test -TARGETS = test_section_manager.exe test_tokenizer.exe -LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ - structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def diff --git a/core/library/tests_configuration/test_section_manager.cpp b/core/library/tests_configuration/test_section_manager.cpp deleted file mode 100644 index c8358237..00000000 --- a/core/library/tests_configuration/test_section_manager.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -* Name : test_section_manager -* Author : Chris Koeritz -* Purpose: Tests that the section manager is writing sections properly and keeping its - table of contents correctly. -** -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -//#define DEBUG_SECTION_MANAGER - // uncomment for debugging version. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -//#ifdef DEBUG_SECTION_MANAGER -// #include -//#endif - -class test_section_manager : public virtual unit_base, virtual public application_shell -{ -public: - test_section_manager() {} - DEFINE_CLASS_NAME("test_section_manager"); - virtual int execute(); -}; - -////////////// - -int test_section_manager::execute() -{ - FUNCDEF("execute"); - { - astring TEST = "First Test"; - ini_configurator ini("t_section_manager_1.ini", ini_configurator::AUTO_STORE); - section_manager mangler(ini, "TOC", "BORK:"); - // clean up the ini file for our testing.... - string_array names; - if (mangler.get_section_names(names)) { - for (int i = 0; i < names.length(); i++) mangler.zap_section(names[i]); - ini.delete_section("TOC"); // remove table of contents. - } - - // now add some entries... - string_table contents1; - contents1.add("oink", "bozoot"); - contents1.add("gink", "rinkum"); - contents1.add("sorty", "figulat"); - contents1.add("crinkish", "wazir"); - ASSERT_TRUE(mangler.add_section("burny", contents1), - TEST + ": couldn't add the first section!"); - string_table temp_1; - ASSERT_TRUE(mangler.find_section("burny", temp_1), - TEST + ": couldn't retrieve the first section!"); -#ifdef DEBUG_SECTION_MANAGER - printf("first section has:\n%s\n", temp_1.text_form().s()); - printf("we want:\n%s\n", contents1.text_form().s()); -#endif - ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect!"); - contents1.add("glurp", "locutusburger"); - ASSERT_FALSE(mangler.add_section("burny", contents1), - TEST + ": incorrectly allowing re-add of first section!"); - ASSERT_TRUE(mangler.replace_section("burny", contents1), - TEST + ": failing to replace first section!"); - temp_1.reset(); - ASSERT_TRUE(mangler.find_section("burny", temp_1), - TEST + ": couldn't retrieve the first section (2)!"); - ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect (2)!"); - - string_table contents2; - contents2.add("tsingha", "tsinglo"); - contents2.add("chunk", "midgets"); - contents2.add("burn", "barns in texas"); - contents2.add("chump", "will not be elected"); - contents2.add("geezerplant", "water weekly"); - ASSERT_TRUE(mangler.add_section("itchy", contents2), - TEST + ": couldn't add the second section!"); - string_table temp_2; - ASSERT_TRUE(mangler.find_section("itchy", temp_2), - TEST + ": couldn't retrieve the second section!"); - ASSERT_EQUAL(temp_2, contents2, TEST + ": second section's contents are incorrect!"); - // test that first section is still there with second having been added. - ASSERT_TRUE(mangler.find_section("burny", temp_1), - TEST + ": couldn't retrieve the first section (3)!"); - ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect (3)!"); - -//more! - } - { -// astring TEST = "Second Test"; - } - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_section_manager, ); - diff --git a/core/library/tests_configuration/test_tokenizer.cpp b/core/library/tests_configuration/test_tokenizer.cpp deleted file mode 100644 index 62b5ea8f..00000000 --- a/core/library/tests_configuration/test_tokenizer.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/* -* Name : test_tokenizer -* Author : Chris Koeritz -* Purpose: Puts the variable_tokenizer through some paces. -** -* Copyright (c) 1998-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -const int MAX_LINE_SIZE = 1000; - // the largest line we will deal with in a file. - -class test_tokenizer : public virtual unit_base, public virtual application_shell -{ -public: - test_tokenizer() {} - DEFINE_CLASS_NAME("test_tokenizer"); - virtual int execute(); -}; - -////////////// - -int test_tokenizer::execute() -{ - FUNCDEF("execute"); - { - astring test_set_1 = "\n\ -[frederick]\n\ -samba=dance\n\ -tantalus rex=gumby\n\ -57 chevy heap=\"16 anagrams of misty immediately\"\n\ -lingus distractus\n\ -shouldus havus assignmentum=\n\ -above better be parsed = 1\n\ -;and this comment too yo\n\ -ted=agent 12\n"; - - astring TEST = "First Test: "; - astring testing = test_set_1; - LOG(astring("file before parsing:") + testing); - variable_tokenizer jed("\n\r", "="); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - variable_tokenizer gorp("\n\r", "="); - ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); - LOG(astring("file after parsing:") + out); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - -//for (int i = 0; i < gorp.table().symbols(); i++) { -//astring name, value; -//gorp.table().retrieve(i, name, value); -//LOG(a_sprintf("item %d: name=\"%s\" value=\"%s\"", i, name.s(), value.s())); -//} - - ASSERT_TRUE(jed.exists("[frederick]"), TEST + "jed section header was omitted!"); - ASSERT_EQUAL(jed.find("[frederick]"), astring(""), - TEST + "jed section header had unexpected contents!"); - ASSERT_EQUAL(jed.find("ted"), astring("agent 12"), - TEST + "jed's ted is missing or invalid!"); - ASSERT_FALSE(jed.find("shouldus havus assignmentum").t(), - TEST + "jed's shouldus had contents but shouldn't!"); - astring value = *jed.table().find("shouldus havus assignmentum"); - ASSERT_EQUAL(value, astring(" "), TEST + "jed shouldus had wrong contents, not special!"); - ASSERT_TRUE(gorp.exists("[frederick]"), TEST + "gorp section header was omitted!"); - ASSERT_EQUAL(gorp.find("[frederick]"), astring(""), - TEST + "gorp section header had unexpected contents!"); - ASSERT_EQUAL(gorp.find("ted"), astring("agent 12"), - TEST + "gorp's ted is missing or invalid!"); - ASSERT_FALSE(gorp.find("shouldus havus assignmentum").t(), - TEST + "gorp's shouldus had contents but shouldn't!"); - value = *gorp.table().find("shouldus havus assignmentum"); - ASSERT_EQUAL(value, astring(" "), TEST + "gorp shouldus had wrong contents, not special!"); - } - { - astring test_set_2 = "Name=SRV, Parent=, Persist=Y, Entry=Y, Required=Y, Desc=Server, Tbl=Server"; - - astring TEST = "Second Test: "; - astring testing = test_set_2; - LOG(astring("file before parsing:") + testing); - variable_tokenizer jed(",", "="); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - LOG(astring("file after parsing:") + out); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - ASSERT_EQUAL(jed.find("Name"), astring("SRV"), TEST + "Name is missing or invalid!"); - ASSERT_FALSE(jed.find("Parent").t(), TEST + "Parent had contents but shouldn't!"); - astring value = *jed.table().find("Parent"); - ASSERT_EQUAL(value, astring(" "), TEST + "Parent had wrong contents, not special!"); - ASSERT_EQUAL(jed.find("Persist"), astring("Y"), TEST + "Persist is missing or invalid!"); - } - - { - astring test_set_3 = "\n\ -[frederick]\n\ -samba=dance\n\ -tantalus rex=gumby \"don#t\n\n'play'\nthat\" homey '\n\ndog\n\n yo \"\ncreen\" arf'\n\ -57 chevy heap=\"16 anagrams of misty immediately\"\n\ -lingus distractus\n\ -shouldus havus assignmentum=\n\ -above better be parsed = 1\n\ -;and this comment too yo\n\ -ted=agent 12\n"; - - astring TEST = "Third Test: "; - astring testing = test_set_3; - LOG(astring("file before parsing:") + testing); - variable_tokenizer jed("\n\r", "=", "\'\""); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - variable_tokenizer gorp("\n\r", "=", "\'\""); - ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); - LOG(astring("file after parsing:") + out); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - ASSERT_TRUE(jed.exists("[frederick]"), TEST + "jed section header was omitted!"); - ASSERT_EQUAL(jed.find("[frederick]"), astring(""), - TEST + "jed section header had unexpected contents!"); - ASSERT_EQUAL(jed.find("ted"), astring("agent 12"), TEST + "jed ted is missing or invalid!"); - ASSERT_FALSE(jed.find("shouldus havus assignmentum").t(), - TEST + "jed shouldus had contents but shouldn't!"); - astring value = *jed.table().find("shouldus havus assignmentum"); - ASSERT_EQUAL(value, astring(" "), TEST + "jed shouldus had wrong contents, not special!"); - ASSERT_TRUE(gorp.exists("[frederick]"), TEST + "gorp section header was omitted!"); - ASSERT_EQUAL(gorp.find("[frederick]"), astring(""), - TEST + "gorp section header had unexpected contents!"); - ASSERT_EQUAL(gorp.find("ted"), astring("agent 12"), TEST + "gorp second ted is missing or invalid!"); - ASSERT_FALSE(gorp.find("shouldus havus assignmentum").t(), - TEST + "gorp shouldus had contents but shouldn't!"); - value = *gorp.table().find("shouldus havus assignmentum"); - ASSERT_EQUAL(value, astring(" "), TEST + "gorp shouldus wrong contents, was not special!"); - ASSERT_TRUE(gorp.exists("tantalus rex"), TEST + "gorp tantalus rex is missing!"); - ASSERT_EQUAL(gorp.find("tantalus rex"), - astring("gumby \"don#t\n\n'play'\nthat\" homey '\n\ndog\n\n yo " - "\"\ncreen\" arf'"), - TEST + "gorp tantalus rex has incorrect contents!"); - } - { - astring test_set_4 = "\n\ -[garfola]\n\ -treadmill=\"this ain't the place\nwhere'n we been done\nseein' no quotes\"\n\ -borfulate='similarly \"we\" do not like\nthe \" quote \" type thing here'\n\ -"; - - astring TEST = "Fourth Test: "; - astring testing = test_set_4; - LOG(astring("file before parsing:\n") + testing); - variable_tokenizer jed("\n\r", "=", "\'\"", false); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - variable_tokenizer gorp("\n\r", "=", "\'\"", false); - ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); - LOG(astring("file after parsing:\n") + out); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - ASSERT_TRUE(gorp.exists("[garfola]"), TEST + "section header was omitted!"); - ASSERT_EQUAL(gorp.find("[garfola]"), astring(""), - TEST + "section header had unexpected contents!"); - ASSERT_TRUE(gorp.exists("treadmill"), TEST + "treadmill is missing!"); - ASSERT_EQUAL(gorp.find("treadmill"), - astring("\"this ain't the place\nwhere'n we been done\nseein' no quotes\""), - TEST + "treadmill has incorrect contents!"); - ASSERT_TRUE(gorp.exists("borfulate"), TEST + "borfulate is missing!"); - ASSERT_EQUAL(gorp.find("borfulate"), - astring("'similarly \"we\" do not like\nthe \" quote \" type thing here'"), - TEST + "borfulate has incorrect contents!"); - } - { - astring test_set_5 = "\n\ - x~35; y~92 ;#comment ; d ~83 ; e~ 54 ; ? new comment ;sud ~ xj23-8 ; nigh ~2"; - - astring TEST = "Fifth Test: "; - astring testing = test_set_5; - LOG(astring("file before parsing:\n") + testing); - variable_tokenizer jed(";", "~"); - jed.set_comment_chars("#?"); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - LOG(astring("file after parsing:\n") + out); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - - variable_tokenizer gorp(";", "~"); - gorp.set_comment_chars("#?"); - ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); - LOG("gorp in tabular form:"); - LOG(gorp.table().text_form()); -//hmmm: need equalizable/orderable on table? - ASSERT_TRUE(gorp.table() == jed.table(), TEST + "gorp text not same as jed!"); - - ASSERT_EQUAL(jed.find("x"), astring("35"), TEST + "value for x missing or invalid"); - ASSERT_EQUAL(jed.find("y"), astring("92"), TEST + "value for y missing or invalid"); - ASSERT_EQUAL(jed.find("d"), astring("83"), TEST + "value for d missing or invalid"); - ASSERT_EQUAL(jed.find("e"), astring("54"), TEST + "value for e missing or invalid"); - ASSERT_EQUAL(jed.find("sud"), astring("xj23-8"), TEST + "value for sud missing or invalid"); - ASSERT_EQUAL(jed.find("nigh"), astring("2"), TEST + "value for nigh missing or invalid"); - } - { - astring test_set_6 = "\r\n\r\n\r\ -# this is yet another test with comments.\r\n\ -; we want to be sure stuff works right.\r\n\ -crumpet=tempest\r\n\ - moomar=18\r\n\ -shagbot =once upon a time there was a man \r\n\ -\t\t\tpunzola megamum =brandle the handle \r\n\ -trapzoot= uhhh\r\n\ -mensch = racer X\r\n\ -\r\n\r\n\r\n"; - - astring TEST = "Sixth Test: "; - astring testing = test_set_6; - LOG(astring("file before parsing:\n===========\n") + testing + "\n==========="); - variable_tokenizer jed("\n\r", "="); - jed.set_comment_chars("#;"); - ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); - astring out; - jed.text_form(out); - LOG(astring("file after parsing:\n===========\n") + out + "\n==========="); - LOG("and in tabular form:"); - LOG(jed.table().text_form()); - - variable_tokenizer gorp("\n\r", "="); - gorp.set_comment_chars("#;"); - ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); - LOG("gorp in tabular form:"); - LOG(gorp.table().text_form()); -LOG(a_sprintf("gorp has %d fields, jed has %d fields", gorp.symbols(), jed.symbols())); - ASSERT_TRUE(gorp.table() == jed.table(), TEST + "gorp text not same as jed!"); - - ASSERT_EQUAL(jed.find("crumpet"), astring("tempest"), - TEST + "value for crumpet missing or invalid"); - ASSERT_EQUAL(jed.find("moomar"), astring("18"), - TEST + "value for moomar missing or invalid"); - ASSERT_EQUAL(jed.find("shagbot"), astring("once upon a time there was a man"), - TEST + "value for shagbot missing or invalid"); - ASSERT_EQUAL(jed.find("trapzoot"), astring("uhhh"), - TEST + "value for trapzoot missing or invalid"); - ASSERT_EQUAL(jed.find("punzola megamum"), astring("brandle the handle"), - TEST + "value for punzola missing or invalid"); - ASSERT_EQUAL(jed.find("mensch"), astring("racer X"), - TEST + "value for mensch missing or invalid"); - } - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_tokenizer, ); - diff --git a/core/library/tests_crypto/makefile b/core/library/tests_crypto/makefile deleted file mode 100644 index d7d3f49e..00000000 --- a/core/library/tests_crypto/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_crypto -TYPE = test -TARGETS = test_blowfish_crypto.exe test_rsa_crypto.exe -LOCAL_LIBS_USED = unit_test crypto application processes loggers configuration textual timely \ - filesystem structures basis -USE_SSL = t -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_crypto/test_blowfish_crypto.cpp b/core/library/tests_crypto/test_blowfish_crypto.cpp deleted file mode 100644 index e6fdd4b0..00000000 --- a/core/library/tests_crypto/test_blowfish_crypto.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* -* Name : test blowfish encryption -* Author : Chris Koeritz -* Purpose: Exercises the BlowFish encryption methods in the crypto library. -** -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace crypto; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) - -//#define DEBUG_BLOWFISH - // uncomment for noisier run. - -const int TEST_RUNS_PER_KEY = 5; // encryption test cycles done on each key. - -const int THREAD_COUNT = 10; // number of threads testing blowfish at once. - -const int ITERATIONS = 4; // number of test runs in our testing threads. - -const int MAX_STRING = 20000; // largest chunk that we'll try to encrypt. - -////////////// - -class test_blowfish; // forward. - -class blowfish_thread : public ethread -{ -public: - blowfish_thread(test_blowfish &parent) : ethread(), _parent(parent) {} - - void perform_activity(void *ptr); - // try out random blowfish keys on randomly chosen chunks of the fodder. - -private: - test_blowfish &_parent; -}; - -////////////// - -class test_blowfish : virtual public unit_base, virtual public application_shell -{ -public: - test_blowfish() - : _fodder(string_manipulation::make_random_name(MAX_STRING + 1, MAX_STRING + 1)) {} - DEFINE_CLASS_NAME("test_blowfish"); - - int execute(); - -private: - astring _fodder; // chunks taken from this are encrypted and decrypted. - time_stamp _program_start; // the time at which we started executing. - thread_cabinet _threads; // manages our testing threads. - friend class blowfish_thread; // bad practice, but saves time in test app. -}; - -int test_blowfish::execute() -{ - FUNCDEF("execute"); - int left = THREAD_COUNT; - while (left--) { - _threads.add_thread(new blowfish_thread(*this), true, NIL); - } - - while (_threads.threads()) { -#ifdef DEBUG_BLOWFISH - LOG(astring("cleaning debris.")); -#endif - _threads.clean_debris(); - time_control::sleep_ms(1000); - } - -#ifdef DEBUG_BLOWFISH - int duration = int(time_stamp().value() - _program_start.value()); - LOG(a_sprintf("duration for %d keys and encrypt/decrypt=%d ms,", - ITERATIONS * TEST_RUNS_PER_KEY * THREAD_COUNT, duration)); - LOG(a_sprintf("that comes to %d ms per cycle.\n", int(double(duration - / TEST_RUNS_PER_KEY / ITERATIONS / THREAD_COUNT)))); -#endif - - return final_report(); -} - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) - -void blowfish_thread::perform_activity(void *) -{ - FUNCDEF("perform_activity"); - int left = ITERATIONS; - while (left--) { - time_stamp key_start; - blowfish_crypto bc(_parent.randomizer().inclusive - (blowfish_crypto::minimum_key_size(), - blowfish_crypto::maximum_key_size())); -#ifdef DEBUG_BLOWFISH - LOG(a_sprintf("%d bit key has:", bc.key_size())); - astring dumped_key = byte_formatter::text_dump(bc.get_key()); - LOG(a_sprintf("%s", dumped_key.s())); -#endif - int key_dur = int(time_stamp().value() - key_start.value()); - -#ifdef DEBUG_BLOWFISH - LOG(a_sprintf(" key generation took %d ms", key_dur)); -#endif - - for (int i = 0; i < TEST_RUNS_PER_KEY; i++) { - byte_array key; - byte_array iv; - - int string_start = _parent.randomizer().inclusive(0, MAX_STRING - 1); - int string_end = _parent.randomizer().inclusive(0, MAX_STRING - 1); - flip_increasing(string_start, string_end); - astring ranstring = _parent._fodder.substring(string_start, string_end); -//LOG(a_sprintf("encoding %s\n", ranstring.s()); -//LOG(a_sprintf("string length encoded: %d\n", ranstring.length()); - - byte_array target; - - time_stamp test_start; - bool worked = bc.encrypt(byte_array(ranstring.length() + 1, - (abyte*)ranstring.s()), target); - int enc_durat = int(time_stamp().value() - test_start.value()); - ASSERT_TRUE(worked, "phase 1 should not fail to encrypt the string"); - - byte_array recovered; - test_start.reset(); - worked = bc.decrypt(target, recovered); - int dec_durat = int(time_stamp().value() - test_start.value()); - ASSERT_TRUE(worked, "phase 1 should not fail to decrypt the string"); -// LOG(a_sprintf("original has %d chars, recovered has %d chars\n", -// ranstring.length(), recovered.length() - 1)); - - astring teddro = (char *)recovered.observe(); -//LOG(a_sprintf("decoded %s\n", teddro.s())); - -#ifdef DEBUG_BLOWFISH - if (teddro != ranstring) { - LOG(a_sprintf("error!\toriginal has %d chars, recovered has %d chars\n", - ranstring.length(), recovered.length() - 1)); - LOG(a_sprintf("\tencoded %s\n", ranstring.s())); - LOG(a_sprintf("\tdecoded %s\n", teddro.s())); - } -#endif - ASSERT_EQUAL(teddro, ranstring, "should not fail to regenerate the original string"); - -#ifdef DEBUG_BLOWFISH - LOG(a_sprintf(" encrypt %d ms, decrypt %d ms, data %d bytes\n", - enc_durat, dec_durat, string_end - string_start + 1)); -#endif - time_control::sleep_ms(0); // take a rest. - } - time_control::sleep_ms(0); // take a rest. - } -} - -HOOPLE_MAIN(test_blowfish, ) - diff --git a/core/library/tests_crypto/test_rsa_crypto.cpp b/core/library/tests_crypto/test_rsa_crypto.cpp deleted file mode 100644 index 56529125..00000000 --- a/core/library/tests_crypto/test_rsa_crypto.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* -* Name : test RSA public key encryption -* Author : Chris Koeritz -* Purpose: -* Exercises the RSA encryption functions from the crypto library. -** -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace crypto; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace processes; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_RSA_CRYPTO - // uncomment for noisy run. - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) - -const int KEY_SIZE = 1024; - // the size of the RSA key that we'll create. - -const int MAX_STRING = 4000; - // the largest chunk that we'll try to encrypt. - -const int THREAD_COUNT = 5; // number of threads testing rsa at once. - -const int ITERATIONS = 6; // number of test runs in our testing threads. - -////////////// - -class test_rsa; // forward. - -class rsa_thread : public ethread -{ -public: - rsa_thread(test_rsa &parent) : ethread(), _parent(parent) {} - - void perform_activity(void *ptr); - // try out random rsa keys on randomly chosen chunks of the fodder. - -private: - test_rsa &_parent; -}; - -////////////// - -class test_rsa : public virtual unit_base, virtual public application_shell -{ -public: - test_rsa() - : _fodder(string_manipulation::make_random_name(MAX_STRING + 1, MAX_STRING + 1)) {} - virtual ~test_rsa() {} - DEFINE_CLASS_NAME("test_rsa"); - - const astring &fodder() const { return _fodder; } - - int execute(); - -private: - astring _fodder; // chunks taken from this are encrypted and decrypted. - time_stamp _program_start; // the time at which we started executing. - thread_cabinet _threads; // manages our testing threads. - friend class rsa_thread; // bad practice, but saves time in test app. -}; - -int test_rsa::execute() -{ - FUNCDEF("execute"); - int left = THREAD_COUNT; - while (left--) { - _threads.add_thread(new rsa_thread(*this), true, NIL); - } - - while (_threads.threads()) { -#ifdef DEBUG_RSA_CRYPTO - LOG(astring("cleaning debris.")); -#endif - _threads.clean_debris(); - time_control::sleep_ms(1000); - } - -#ifdef DEBUG_RSA_CRYPTO - int duration = int(time_stamp().value() - _program_start.value()); - LOG(a_sprintf("duration for %d keys and encrypt/decrypt=%d ms,", - ITERATIONS * THREAD_COUNT, duration)); - LOG(a_sprintf("that comes to %d ms per cycle.", int(double(duration - / ITERATIONS / THREAD_COUNT)))); -#endif - - return final_report(); -} - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) - -void rsa_thread::perform_activity(void *) -{ - FUNCDEF("perform_activity"); - int left = ITERATIONS; - while (left--) { - time_stamp start; - - rsa_crypto rc_private_here(KEY_SIZE); - int key_durat = int(time_stamp().value() - start.value()); - - byte_array public_key; - rc_private_here.public_key(public_key); // get our public portion. - byte_array private_key; - rc_private_here.private_key(private_key); // get our private portion. - -//RSA_print_fp(stdout, private_key, 0); -//RSA_print_fp(stdout, public_key, 0); - - int string_start = _parent.randomizer().inclusive(0, MAX_STRING); - int string_end = _parent.randomizer().inclusive(0, MAX_STRING); - flip_increasing(string_start, string_end); - astring ranstring = _parent.fodder().substring(string_start, string_end); - byte_array target; - - // the first phase tests the outsiders sending back data that only we, - // with our private key, can decrypt. - - start.reset(); - rsa_crypto rc_pub(public_key); - bool worked = rc_pub.public_encrypt(byte_array(ranstring.length() + 1, - (abyte*)ranstring.s()), target); - int pub_enc_durat = int(time_stamp().value() - start.value()); - ASSERT_TRUE(worked, "phase 1 shouldn't fail to encrypt the string"); - - rsa_crypto rc_priv(private_key); - byte_array recovered; - start.reset(); - worked = rc_priv.private_decrypt(target, recovered); - int priv_dec_durat = int(time_stamp().value() - start.value()); - ASSERT_TRUE(worked, "phase 1 should not fail to decrypt the string"); - - astring teddro = (char *)recovered.observe(); - - ASSERT_EQUAL(teddro, ranstring, "should not fail to get back the data"); - - // the second phase tests us using our private key to encrypt data which - // anyone with the public key can decode. - - start.reset(); - worked = rc_priv.private_encrypt(byte_array(ranstring.length() + 1, - (abyte*)ranstring.s()), target); - int priv_enc_durat = int(time_stamp().value() - start.value()); - ASSERT_TRUE(worked, "phase 2 should not fail to encrypt the string"); - - start.reset(); - worked = rc_pub.public_decrypt(target, recovered); - int pub_dec_durat = int(time_stamp().value() - start.value()); - ASSERT_TRUE(worked, "phase 2 should not fail to decrypt the string"); - - teddro = (char *)recovered.observe(); - - ASSERT_EQUAL(teddro, ranstring, "should not fail to get back the data here either"); - -#ifdef DEBUG_RSA_CRYPTO - LOG(a_sprintf("key generation: %d ms, public encrypt: %d ms, private " - "decrypt: %d ms", key_durat, pub_enc_durat, priv_dec_durat)); - LOG(a_sprintf("data size: %d bytes, private encrypt: %d ms, public " - "decrypt: %d ms", - string_end - string_start + 1, priv_enc_durat, pub_dec_durat)); -#endif - - time_control::sleep_ms(0); // take a rest. - } -} - -HOOPLE_MAIN(test_rsa, ) - diff --git a/core/library/tests_filesystem/makefile b/core/library/tests_filesystem/makefile deleted file mode 100644 index b517463e..00000000 --- a/core/library/tests_filesystem/makefile +++ /dev/null @@ -1,13 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_filesystem -TYPE = test -TARGETS = test_byte_filer.exe test_directory.exe test_directory_tree.exe test_file_info.exe \ - test_file_time.exe test_filename.exe test_huge_file.exe -DEFINITIONS += USE_HOOPLE_DLLS -LOCAL_LIBS_USED = unit_test application configuration filesystem loggers mathematics nodes \ - structures textual timely structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_filesystem/test_byte_filer.cpp b/core/library/tests_filesystem/test_byte_filer.cpp deleted file mode 100644 index 8261d398..00000000 --- a/core/library/tests_filesystem/test_byte_filer.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_byte_filer * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -class test_byte_filer : virtual public unit_base, virtual public application_shell -{ -public: - test_byte_filer() : application_shell() {} - DEFINE_CLASS_NAME("test_byte_filer"); - int run_simple_test(); - int run_file_scan(); - virtual int execute(); -}; - -const astring &TEST_FILE() -{ - const char *TEST_FILE_BASE = "/zz_garbage.txt"; - static astring __hidden_filename; - if (!__hidden_filename) { - __hidden_filename = environment::get("TMP"); - if (!__hidden_filename) __hidden_filename = "/tmp"; - __hidden_filename += astring(TEST_FILE_BASE); - } - return __hidden_filename; -} - -int test_byte_filer::run_simple_test() -{ - FUNCDEF("run_simple_test"); -#ifdef DEBUG - LOG("ahoy, beginning file test..."); - LOG(astring("test file is ") + TEST_FILE()); -#endif - - chaos randomizer; - -//hmmm: move to t_filename. - // test filename's exist operation. - byte_filer garbage(TEST_FILE().s(), "wb"); - garbage.write("oy.\n"); - garbage.close(); - filename test1(TEST_FILE()); - ASSERT_TRUE(test1.exists(), "exists test file should exist"); - filename test2("c:\\this_file_shouldNt_exist_ever.txt"); - ASSERT_FALSE(test2.exists(), "weird file should not existed"); - // test again to make sure it didn't create it. - ASSERT_FALSE(test2.exists(), "weird file should still not exist"); - test1.unlink(); - - int block_size = randomizer.inclusive(3000, 30000); -#ifdef DEBUG - LOG(a_sprintf("block size=%d", block_size)); -#endif - abyte *original_block = new abyte[block_size]; - for (int i = 0; i < block_size; i++) - original_block[i] = abyte(randomizer.inclusive(32, 126)); - unsigned int original_checksum - = checksums::bizarre_checksum((abyte *)original_block, block_size); - if (original_checksum) {} // compiler quieting. -#ifdef DEBUG - LOG(a_sprintf("random block checksum=%d", original_checksum)); -#endif - { - byte_array to_stuff_in_file(block_size, original_block); - delete [] original_block; - byte_filer fred(TEST_FILE(), "w+"); - fred.write(to_stuff_in_file); - } -#ifdef DEBUG - LOG(astring("about to compare file to checksum")); -#endif - { - abyte *temp_array = new abyte[21309]; - byte_array to_fake_stuff(21309, temp_array); - delete [] temp_array; - byte_filer fred(TEST_FILE(), "r"); -#ifdef DEBUG - LOG(astring("about to try writing to file")); -#endif - int should_be_failure = fred.write(to_fake_stuff); - ASSERT_EQUAL(should_be_failure, 0, "write on read only, should not succeed"); - -/// int fredsize = int(fred.size()); -/// fred.chunk_factor(fredsize); - -#ifdef DEBUG - LOG(a_sprintf("about to try reading from file %d bytes", fredsize)); -#endif - byte_array file_contents; - int bytes_read = fred.read(file_contents, block_size * 2); - ASSERT_EQUAL(bytes_read, block_size, "reading entire file should get proper size"); - un_int check_2 = checksums::bizarre_checksum((abyte *)file_contents.access(), file_contents.length()); - ASSERT_EQUAL((int)check_2, (int)original_checksum, "should read correct contents for checksum"); - } - -#define FACTOR 1354 - - { - int numpacs = number_of_packets(block_size, FACTOR); - byte_filer fred(TEST_FILE(), "rb"); -///file::READ_ONLY); -/// fred.chunk_factor(FACTOR); - int whole_size = 0; - for (int i = 0; i < numpacs; i++) { - byte_array blob_i; - int bytes_in = fred.read(blob_i, FACTOR); - ASSERT_FALSE(bytes_in > FACTOR, "we should never somehow read in more than we asked for"); - whole_size += blob_i.length(); - } - ASSERT_EQUAL(whole_size, fred.length(), "chunking comparison should see sizes as same"); - } - -// test writing out a copy and comparing them... there's no == on files! - - ASSERT_TRUE(filename(TEST_FILE()).unlink(), "cleanup should be able to remove temporary file"); - - // it seems everything worked during our tests. - return 0; -} - -int test_byte_filer::run_file_scan() -{ - FUNCDEF("run_file_scan"); - chaos randomizer; - - string_array files(_global_argc, (const char **)_global_argv); - files.zap(0, 0); // toss the first element since that's our app filename. - - if (!files.length()) { - // pretend they gave us the list of files in the TMP directory. some of - // these might fail if they're locked up. -// astring tmpdir = environment::get("TMP"); - astring tmpdir = application_configuration::current_directory(); - directory dir(tmpdir); - for (int i = 0; i < dir.files().length(); i++) { - // skip text files since we use those right here. - if ( (dir.files()[i].ends(".txt")) || (dir.files()[i].ends(".txt")) ) - continue; - astring chewed_string = tmpdir + "/" + dir.files()[i]; - files += chewed_string; - } -//LOG(astring("added files since no cmd args: ") + files.text_form()); - } - - byte_array data_found; - for (int i = 0; i < files.length(); i++) { - astring curr = files[i]; -// LOG(a_sprintf("file %d: ", i) + curr); - byte_filer test(curr, "rb"); - if (!test.good()) { - LOG(astring("good check: ") + curr + " cannot be opened. is this bad?"); - continue; - } - - // check that we get the expected position report from scooting to the - // end of a file. - test.seek(0, byte_filer::FROM_END); - ASSERT_EQUAL((int)test.tell(), (int)test.length(), "seek check should get to end as expected"); - test.seek(0, byte_filer::FROM_START); - - size_t len = test.length(); -//log(a_sprintf("file len is %.0f", double(len))); - size_t posn = 0; - while ( (posn < len) && !test.eof() ) { - size_t readlen = randomizer.inclusive(1, 256 * KILOBYTE); -//log(a_sprintf("read %u bytes, posn now %d bytes", readlen, posn)); - int bytes_read = int(test.read(data_found, int(readlen))); - ASSERT_TRUE(bytes_read >= 0, "reading should not fail to read some bytes"); - if (bytes_read > 0) { - posn += bytes_read; - } - } - ASSERT_TRUE(test.eof(), "eof check should see us at eof"); - ASSERT_EQUAL((int)posn, (int)len, "eof check should be at right position"); -// log(astring("successfully read ") + curr); - } - - return 0; -} - -int test_byte_filer::execute() -{ -// FUNCDEF("execute"); - int ret = run_simple_test(); - if (ret) return ret; // failed. - ret = run_file_scan(); - if (ret) return ret; // failed here. - - return final_report(); -} - -HOOPLE_MAIN(test_byte_filer, ) - diff --git a/core/library/tests_filesystem/test_directory.cpp b/core/library/tests_filesystem/test_directory.cpp deleted file mode 100644 index e2e70165..00000000 --- a/core/library/tests_filesystem/test_directory.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* -* Name : test_directory -* Author : Chris Koeritz -* Purpose: -* Tests the directory object out to see if it scans properly. -** -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -////////////// - -class test_directory : public virtual unit_base, public virtual application_shell -{ -public: - test_directory() : application_shell() {} - DEFINE_CLASS_NAME("test_directory"); - int execute(); -}; - -////////////// - -int test_directory::execute() -{ - FUNCDEF("execute"); - { - astring path = "/tmp"; // default path. -#ifdef __WIN32__ - path = "c:/"; // default path for windoze. -#endif - if (application::_global_argc >= 2) - path = application::_global_argv[1]; - - astring pattern = "*"; - if (application::_global_argc >= 3) - pattern = application::_global_argv[2]; - -// log(astring("Scanning directory named \"") + path + "\""); -// log(astring("Using pattern-match \"") + pattern + "\""); - - directory dir(path, pattern.s()); - ASSERT_TRUE(dir.good(), "the current directory should be readable"); -// log(path + " contained these files:"); - astring names; - for (int i = 0; i < dir.files().length(); i++) { - names += dir.files()[i] + " "; - } - astring split; - string_manipulation::split_lines(names, split, 4); -// log(split); -// log(path + " contained these directories:"); - names = ""; - for (int i = 0; i < dir.directories().length(); i++) { - names += dir.directories()[i] + " "; - } - string_manipulation::split_lines(names, split, 4); -// log(split); - } -//hmmm: the above test proves zilch. -// it needs to do this differently. -// instead of relying on someone else's folder, pick and make our own. -// then fill it with some known stuff. -// verify then that the read form is identical! - - - -//more tests! - - return final_report(); -} - -HOOPLE_MAIN(test_directory, ) - diff --git a/core/library/tests_filesystem/test_directory_tree.cpp b/core/library/tests_filesystem/test_directory_tree.cpp deleted file mode 100644 index c1894aca..00000000 --- a/core/library/tests_filesystem/test_directory_tree.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* -* Name : test_directory_tree -* Author : Chris Koeritz -* Purpose: -* Tests the directory_tree object on some well-known directories. -** -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -const bool JUST_SIZES = false; - // determines if we'll only compare file size and time. - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -class test_directory_tree : public virtual unit_base, virtual public application_shell -{ -public: - test_directory_tree() : application_shell() {} - DEFINE_CLASS_NAME("test_directory_tree"); - int execute(); -}; - -int test_directory_tree::execute() -{ - FUNCDEF("execute"); - - astring path = "/usr/include"; -#ifdef __WIN32__ - // default path for windoze uses an area that should always exist. - path = environment::get("COMMONPROGRAMFILES"); -#endif - - // process the command line parameters, which are optionally a directory name and - // a pattern to use when scanning. - if (_global_argc >= 2) - path = _global_argv[1]; - - astring pattern = "*"; - if (_global_argc >= 3) - pattern = _global_argv[2]; - - { -// log(astring("Scanning directory tree at \"") + path + "\""); -// log(astring("Using pattern-match \"") + pattern + "\""); - - directory_tree dir(path, pattern.s()); - ASSERT_TRUE(dir.good(), "directory_tree construction should succeed and be readable."); - - dir_tree_iterator *ted = dir.start(directory_tree::prefix); - // create our iterator to do a prefix traversal. - - int depth; // current depth in tree. - filename curr; // the current path the iterator is at. - string_array files; // the filenames held at the iterator. - - while (directory_tree::current(*ted, curr, files)) { - // we have a good directory to show. - directory_tree::depth(*ted, depth); -// log(string_manipulation::indentation(depth * 2) + astring("[") -// + curr.raw() + "]"); - astring names; - for (int i = 0; i < files.length(); i++) names += files[i] + " "; - if (names.length()) { - astring split; - string_manipulation::split_lines(names, split, depth * 2 + 2); -// log(split); - } - - // go to the next place. - directory_tree::next(*ted); - } - - directory_tree::throw_out(ted); - } - - { - // second test group. seek operation. -//scan the directory, create some temporary directories and junk filenames -//therein, then seek to that location. - - } - - { - // third test group. tree comparison operation. -// log(astring("Self-comparing directory tree at \"") + path + "\""); -// log(astring("Using pattern-match \"") + pattern + "\""); - -// LOG("reading tree 1."); - directory_tree dir(path, pattern.s()); - ASSERT_TRUE(dir.good(), "the directory should be readable for self-compare"); - - // now read a copy of the tree also. -// LOG("reading tree 2."); - directory_tree dir2(path, pattern.s()); - ASSERT_TRUE(dir2.good(), "the directory should read the second time fine too"); - -// LOG("comparing the two trees."); - filename_list diffs; - directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); -//LOG(diffs.text_form()); - - ASSERT_FALSE(diffs.elements(), "there should be no differences comparing identical dirs"); - } - - { - // fourth test: see if the calculate function works. -// log(astring("Calculating sums for tree at \"") + path + "\""); -// log(astring("Using pattern-match \"") + pattern + "\""); - -// LOG("reading tree 1."); - directory_tree dir(path, pattern.s()); - ASSERT_TRUE(dir.good(), "the directory should be readable for checksums"); - - // now read a copy of the tree also. -// LOG("reading tree 2."); - directory_tree dir2(path, pattern.s()); - ASSERT_TRUE(dir2.good(), "checksummer should be able to read second time also"); - -// LOG("calculating checksums for tree 1."); - ASSERT_TRUE(dir.calculate(JUST_SIZES), "the first checksummer tree can be calculated"); - -// LOG("calculating checksums for tree 2."); - ASSERT_TRUE(dir2.calculate(JUST_SIZES), "the second checksummer tree can be calculated"); - -// LOG("comparing the two trees."); - filename_list diffs; - directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); -//LOG(diffs.text_form()); - - ASSERT_FALSE(diffs.elements(), "no checksummer differences should be seen for identical directories"); - } - - { - // fifth test: see if the packing works. -// log(astring("Reading tree for packing at \"") + path + "\""); -// log(astring("Using pattern-match \"") + pattern + "\""); - -// LOG("reading tree."); - directory_tree dir(path, pattern.s()); - ASSERT_TRUE(dir.good(), "packer directory should be read"); - -// LOG("calculating checksums for tree."); - ASSERT_TRUE(dir.calculate(JUST_SIZES), "the first packer tree can be calculated"); - - byte_array packed_form; - int size_packed = dir.packed_size(); - dir.pack(packed_form); -//LOG(a_sprintf("tree became %d abyte array", packed_form.length())); - ASSERT_EQUAL(size_packed, packed_form.length(), "packed size should be right"); - - directory_tree dir2; - ASSERT_TRUE(dir2.unpack(packed_form), "second tree can be unpacked from the first"); - -// LOG("comparing the two trees."); - filename_list diffs; - directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); -//LOG(diffs.text_form()); - - ASSERT_FALSE(diffs.elements(), "identical directories should stay same after packing"); - - directory_tree::compare_trees(dir2, dir, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); - ASSERT_FALSE(diffs.elements(), "no differences for reverse compare identical dirs"); - } - -// nth test: -// combine the results of the second test with a comparison like in the -// third test. delete all of those temporary files that were added. -// rescan tree. make sure that a tree containing the temporaries -// when compared with the current post-deletion tree produces a list -// that contains all the temporary files and directories. - - -//hmmm: more tests! - - return final_report(); -} - -HOOPLE_MAIN(test_directory_tree, ) - diff --git a/core/library/tests_filesystem/test_file_info.cpp b/core/library/tests_filesystem/test_file_info.cpp deleted file mode 100644 index cc0a41d2..00000000 --- a/core/library/tests_filesystem/test_file_info.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* -* Name : test_file_info -* Author : Chris Koeritz -* Copyright (c) 1991-$now By Author. This program is free software; you can * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_TEST_FILE_INFO - // uncomment for noisy version. - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -static chaos a_randomizer; - -////////////// - -class test_file_info : public application_shell, public unit_base -{ -public: - test_file_info() : application_shell(), unit_base() {} - - DEFINE_CLASS_NAME("test_file_info"); - - virtual int execute(); -}; - -////////////// - -//hmmm: stolen from ssl_init. -byte_array random_bytes(int length) -{ - byte_array seed; - for (int i = 0; i < length; i++) - seed += abyte(chaos().inclusive(0, 255)); - return seed; -} - -int test_file_info::execute() -{ - FUNCDEF("execute"); -#ifdef __UNIX__ - file_time absurdity_time("/"); -#endif -#ifdef __WIN32__ - file_time absurdity_time("c:/"); -#endif - - // test storing info via the constructor. - file_info testing(filename("/usr/schrodingers/dog/got/away"), 7298238); - testing._time = absurdity_time; - testing._checksum = 1283412; - ASSERT_EQUAL((int)testing._file_size, (int)7298238, "constructor file size"); - ASSERT_EQUAL(testing._time, absurdity_time, "constructor file time"); - ASSERT_EQUAL(testing._checksum, 1283412, "constructor checksum"); - ASSERT_EQUAL((filename &)testing, filename("/usr/schrodingers/dog/got/away"), - "constructor filename"); - - // test packing the object and packed_size. - byte_array packed; - int size = testing.packed_size(); - testing.pack(packed); - ASSERT_EQUAL(size, packed.length(), "basic packed size accuracy"); - file_info unstuffy; - ASSERT_TRUE(unstuffy.unpack(packed), "basic unpacking"); - - // test validity after unpacking. - ASSERT_EQUAL((int)unstuffy._file_size, (int)7298238, "constructor file size"); - ASSERT_EQUAL(unstuffy._time, absurdity_time, "constructor file time"); - ASSERT_EQUAL(unstuffy._checksum, 1283412, "constructor checksum"); - ASSERT_EQUAL((filename &)unstuffy, filename("/usr/schrodingers/dog/got/away"), - "constructor filename"); - - // test the extra bits, the attachment and secondary name. - astring seconame = "glorabahotep"; - testing.secondary(seconame ); - const byte_array randobytes = random_bytes(chaos().inclusive(37, 4128)); - testing.attachment(randobytes); - packed.reset(); - size = testing.packed_size(); - testing.pack(packed); - ASSERT_EQUAL(size, packed.length(), "secondary packed size accuracy"); - ASSERT_TRUE(unstuffy.unpack(packed), "secondary unpacking"); - // test that the secondary name and attachment came back. - ASSERT_EQUAL(seconame, unstuffy.secondary(), "secondary name incorrect"); - ASSERT_EQUAL(randobytes, unstuffy.attachment(), "secondary attachment inaccurate"); - - return final_report(); -} - -HOOPLE_MAIN(test_file_info, ) - diff --git a/core/library/tests_filesystem/test_file_time.cpp b/core/library/tests_filesystem/test_file_time.cpp deleted file mode 100644 index 6e79f9a2..00000000 --- a/core/library/tests_filesystem/test_file_time.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* -* Name : test_file_time -* Author : Chris Koeritz -* Copyright (c) 1991-$now By Author. This program is free software; you can * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef __UNIX__ - #include -#endif - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_TEST_FILE_INFO - // uncomment for noisy version. - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -static chaos a_randomizer; - -////////////// - -class test_file_time : public application_shell, public unit_base -{ -public: - test_file_time() : application_shell(), unit_base() {} - - DEFINE_CLASS_NAME("test_file_time"); - - virtual int execute(); -}; - -////////////// - -int test_file_time::execute() -{ - FUNCDEF("execute"); - -#ifdef __UNIX__ - astring toppy("/"); -#endif -#ifdef __WIN32__ - astring toppy("c:/ntldr"); // will work for any modern windows OS. -#endif - - // test storing info via the constructor. - file_time absurdity_time(toppy); - FILE *topdir = fopen(toppy.s(), "r"); - file_time nutty_time(topdir); - struct stat sbuffer; - int filenum = fileno(topdir); - int stat_okay = fstat(filenum, &sbuffer); - ASSERT_FALSE(stat_okay, "failure to read filetime"); - file_time goofy_time(sbuffer.st_mtime); - fclose(topdir); - file_time testing(goofy_time); // copy ctor. - // test that they all got the same idea from the file. - ASSERT_EQUAL(absurdity_time, nutty_time, "filename vs. FILE ctor"); - ASSERT_EQUAL(absurdity_time, goofy_time, "filename vs. time_t ctor"); - ASSERT_EQUAL(absurdity_time, testing, "filename vs. copy ctor"); - ASSERT_EQUAL(nutty_time, goofy_time, "FILE vs. time_t ctor"); - ASSERT_EQUAL(nutty_time, testing, "FILE vs. copy ctor"); - // one reversed direction check. - ASSERT_EQUAL(goofy_time, absurdity_time, "time_t vs. filename ctor"); - - // test packing the object and packed_size. - byte_array packed; - int size = testing.packed_size(); - testing.pack(packed); - ASSERT_EQUAL(size, packed.length(), "packed size accuracy"); - file_time unstuffy; - ASSERT_TRUE(unstuffy.unpack(packed), "unpacking"); - ASSERT_EQUAL((double)testing.raw(), (double)unstuffy.raw(), "unpacked contents should be equal to prior"); - - // test the text_form method. - astring text; - testing.text_form(text); - ASSERT_INEQUAL(text.length(), 0, "text_form produces text"); - - // test validity after unpacking. - ASSERT_EQUAL(unstuffy, goofy_time, "constructor file size"); - - return final_report(); -} - -HOOPLE_MAIN(test_file_time, ) - diff --git a/core/library/tests_filesystem/test_filename.cpp b/core/library/tests_filesystem/test_filename.cpp deleted file mode 100644 index 8f08de45..00000000 --- a/core/library/tests_filesystem/test_filename.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* -* Name : test_filename -* Author : Chris Koeritz -** -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#define DEBUG_FILENAME_TEST - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -class test_filename : virtual public unit_base, public virtual application_shell -{ -public: - test_filename() : application_shell() {} - DEFINE_CLASS_NAME("test_filename"); - virtual int execute(); - void clean_sequel(astring &sequel); -}; - -void test_filename::clean_sequel(astring &sequel) -{ sequel.replace_all('\\', '/'); } - -int test_filename::execute() -{ - FUNCDEF("execute") - { - // first test group. - filename gorgeola(""); - ASSERT_FALSE(gorgeola.exists(), "an empty filename should not exist"); - } - - { - // second test group. - astring GROUP = "separate-- "; - filename turkey("/omega/ralph/turkey/buzzard.txt"); - string_array pieces; - turkey.separate(pieces); - ASSERT_TRUE(pieces[1].equal_to("omega"), GROUP + "the first piece didn't match."); - ASSERT_TRUE(pieces[2].equal_to("ralph"), GROUP + "the second piece didn't match."); - ASSERT_TRUE(pieces[3].equal_to("turkey"), GROUP + "the third piece didn't match."); - ASSERT_TRUE(pieces[4].equal_to("buzzard.txt"), GROUP + "the fourth piece didn't match."); - ASSERT_EQUAL(pieces.length(), 5, GROUP + "the list was the wrong length"); - } - - { - // third test group. - astring GROUP = "third: test compare_prefix "; - filename turkey("/omega/ralph/turkey/buzzard.txt"); - filename murpin1("/omega"); - filename murpin2("/omega/ralph"); - filename murpin3("/omega/ralph/turkey"); - filename murpin4("/omega/ralph/turkey/buzzard.txt"); - filename murpin_x1("ralph/turkey/buzzard.txt"); - filename murpin_x2("/omega/ralph/turkey/buzzard.txt2"); - filename murpin_x3("/omega/turkey/buzzard.txt"); - filename murpin_x4("/omega/ralph/turkey/b0/buzzard.txt"); - filename murpin_x5("moomega/ralph/turkey"); - - astring sequel; - ASSERT_TRUE(murpin1.compare_prefix(turkey, sequel), GROUP + "first should match but didn't"); - clean_sequel(sequel); - ASSERT_TRUE(sequel.equal_to("ralph/turkey/buzzard.txt"), GROUP + "first sequel was wrong"); - ASSERT_TRUE(murpin2.compare_prefix(turkey, sequel), GROUP + "second should match but didn't"); - clean_sequel(sequel); - ASSERT_TRUE(sequel.equal_to("turkey/buzzard.txt"), GROUP + "second sequel was wrong"); - ASSERT_TRUE(murpin3.compare_prefix(turkey, sequel), GROUP + "third should match but didn't"); - clean_sequel(sequel); - ASSERT_TRUE(sequel.equal_to("buzzard.txt"), GROUP + "third sequel was wrong"); - ASSERT_TRUE(murpin4.compare_prefix(turkey, sequel), GROUP + "fourth should match but didn't"); - ASSERT_FALSE(sequel.t(), GROUP + "fourth had a sequel but shouldn't"); - - ASSERT_FALSE(murpin_x1.compare_prefix(turkey, sequel), - GROUP + "x-first should not match but did"); - ASSERT_FALSE(sequel.t(), - GROUP + "x-first had a sequel but shouldn't"); - ASSERT_FALSE(murpin_x2.compare_prefix(turkey, sequel), - GROUP + "x-second should not match but did"); - ASSERT_FALSE(sequel.t(), - GROUP + "x-second had a sequel but shouldn't"); - ASSERT_FALSE(murpin_x3.compare_prefix(turkey, sequel), - GROUP + "x-third should not match but did"); - ASSERT_FALSE(sequel.t(), - GROUP + "x-third had a sequel but shouldn't"); - ASSERT_FALSE(murpin_x4.compare_prefix(turkey, sequel), - GROUP + "x-fourth should not match but did"); - ASSERT_FALSE(sequel.t(), - GROUP + "x-fourth had a sequel but shouldn't"); - ASSERT_FALSE(murpin_x5.compare_prefix(turkey, sequel), - GROUP + "x-fifth should not match but did"); - ASSERT_FALSE(sequel.t(), - GROUP + "x-fifth had a sequel but shouldn't"); - - // check that the functions returning no sequel are still correct. - ASSERT_TRUE(murpin1.compare_prefix(turkey), GROUP + "the two versions differed!"); - ASSERT_FALSE(murpin_x1.compare_prefix(turkey), GROUP + "x-the two versions differed!"); - } - - { - // fourth test group. - astring GROUP = "fourth: test compare_suffix "; - filename turkey("/omega/ralph/turkey/buzzard.txt"); - filename murpin1("turkey\\buzzard.txt"); - filename murpin2("turkey/buzzard.txt"); - filename murpin3("ralph/turkey/buzzard.txt"); - filename murpin4("omega/ralph/turkey/buzzard.txt"); - filename murpin5("/omega/ralph/turkey/buzzard.txt"); - - ASSERT_TRUE(murpin1.compare_suffix(turkey), GROUP + "compare 1 failed"); - ASSERT_TRUE(murpin2.compare_suffix(turkey), GROUP + "compare 2 failed"); - ASSERT_TRUE(murpin3.compare_suffix(turkey), GROUP + "compare 3 failed"); - ASSERT_TRUE(murpin4.compare_suffix(turkey), GROUP + "compare 4 failed"); - ASSERT_TRUE(murpin5.compare_suffix(turkey), GROUP + "compare 5 failed"); - - ASSERT_FALSE(turkey.compare_suffix(murpin1), GROUP + "compare x.1 failed"); - } - - { - // fifth test group. - // tests out the canonicalization method on any parameters given on - // the command line, including the program name. - astring GROUP = "fifth: canonicalize command-line paths "; -// log(GROUP, ALWAYS_PRINT); - for (int i = 0; i < application::_global_argc; i++) { - filename canony(application::_global_argv[i]); -// log(a_sprintf("parm %d:\n\tfrom \"%s\"\n\t to \"%s\"", i, application::_global_argv[i], -// canony.raw().s()), ALWAYS_PRINT); - -//hmmm: the above wasn't really a test so much as a look at what we did. -// we should run the canonicalizer against a set of known paths so we can know what to -// expect. - - } - } - - { - // sixth test group. - astring GROUP = "sixth: testing pop and push "; - // test dossy paths. - filename test1("c:/flug/blumen/klemper/smooden"); -//log(astring("base=") + test1.basename(), ALWAYS_PRINT); - ASSERT_EQUAL(test1.basename(), astring("smooden"), GROUP + "basename 1 failed"); -//log(astring("got past basename 1 test that was failing.")); - ASSERT_EQUAL(test1.dirname(), filename("c:/flug/blumen/klemper"), - GROUP + "d-dirname 1 failed"); -//log(astring("got past a test or so after that.")); - filename test2 = test1; - astring popped = test2.pop(); - ASSERT_EQUAL(popped, astring("smooden"), GROUP + "dpop 1 return failed"); - ASSERT_EQUAL(test2, filename("c:/flug/blumen/klemper"), GROUP + "dpop 1 failed"); - test2.pop(); - test2.pop(); - ASSERT_EQUAL(test2, filename("c:/flug"), GROUP + "dpop 2 failed"); - popped = test2.pop(); - ASSERT_EQUAL(popped, astring("flug"), GROUP + "dpop 1 return failed"); - ASSERT_EQUAL(test2, filename("c:/"), GROUP + "dpop 3 failed"); - test2.pop(); - ASSERT_EQUAL(test2, filename("c:/"), GROUP + "dpop 3 failed"); - test2.push("flug"); - test2.push("blumen"); - test2.push("klemper"); - ASSERT_EQUAL(test2, filename("c:/flug/blumen/klemper"), GROUP + "dpush 1 failed"); - // test unix paths. - filename test3("/flug/blumen/klemper/smooden"); - ASSERT_EQUAL(test3.basename(), astring("smooden"), GROUP + "basename 1 failed"); - ASSERT_EQUAL(test3.dirname(), filename("/flug/blumen/klemper"), - GROUP + "u-dirname 1 failed"); - filename test4 = test3; - popped = test4.pop(); - ASSERT_EQUAL(popped, astring("smooden"), GROUP + "upop 1 return failed"); - ASSERT_EQUAL(test4, filename("/flug/blumen/klemper"), GROUP + "upop 1 failed"); - test4.pop(); - test4.pop(); - ASSERT_EQUAL(test4, filename("/flug"), GROUP + "upop 2 failed"); - popped = test4.pop(); - ASSERT_EQUAL(popped, astring("flug"), GROUP + "upop 1 return failed"); - ASSERT_EQUAL(test4, filename("/"), GROUP + "upop 3 failed"); - test4.pop(); - ASSERT_EQUAL(test4, filename("/"), GROUP + "upop 3 failed"); - test4.push("flug"); - test4.push("blumen"); - test4.push("klemper"); - ASSERT_EQUAL(test4, filename("/flug/blumen/klemper"), GROUP + "upush 1 failed"); - } - { - // seventh test group. - astring GROUP = "seventh: testing pack and unpack "; - filename test1("/usr/local/athabasca"); - byte_array packed; - int size_guess = test1.packed_size(); - test1.pack(packed); - ASSERT_EQUAL(size_guess, packed.length(), GROUP + "packed_size 1 failed"); - filename test2; - ASSERT_TRUE(test2.unpack(packed), GROUP + "unpack 1 failed"); - ASSERT_EQUAL(test2, test1, GROUP + "packed contents differ, 1 failed"); - } - - return final_report(); -} - -HOOPLE_MAIN(test_filename, ) - diff --git a/core/library/tests_filesystem/test_huge_file.cpp b/core/library/tests_filesystem/test_huge_file.cpp deleted file mode 100644 index f9209eb7..00000000 --- a/core/library/tests_filesystem/test_huge_file.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* -* Name : test_huge_file -* Author : Chris Koeritz -** -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) - -class test_huge_file : public virtual unit_base, virtual public application_shell -{ -public: - test_huge_file() : application_shell() {} - DEFINE_CLASS_NAME("test_huge_file"); - void run_file_scan(); - virtual int execute(); -}; - -void test_huge_file::run_file_scan() -{ - FUNCDEF("run_file_scan"); - chaos randomizer; - - string_array files(application::_global_argc, (const char **)application::_global_argv); - files.zap(0, 0); // toss the first element since that's our app filename. - - if (!files.length()) { - // pretend they gave us the list of files in the TMP directory. some of - // these might fail if they're locked up. -// astring tmpdir = environment::get("TMP"); - astring tmpdir = application_configuration::current_directory(); - directory dir(tmpdir); - for (int i = 0; i < dir.files().length(); i++) { - // skip text files since we use those right here. - if (dir.files()[i].ends(".txt")) - continue; - astring chewed_string = tmpdir + "/" + dir.files()[i]; - files += chewed_string; - } -//LOG(astring("added files since no cmd args: ") + files.text_form()); - } - - byte_array data_found; - for (int i = 0; i < files.length(); i++) { - astring curr = files[i]; -// LOG(a_sprintf("file %d: ", i) + curr); - huge_file test(curr, "rb"); - ASSERT_TRUE(test.good(), "good check should say yes, it's good."); - double len = test.length(); -//log(a_sprintf("file len is %.0f", len)); - double posn = 0; - while ( (posn < len) && !test.eof() ) { - int readlen = randomizer.inclusive(1, 256 * KILOBYTE); -//log(a_sprintf("read %.0f bytes, posn now %.0f bytes", double(readlen), posn)); - int bytes_read = 0; - outcome ret = test.read(data_found, readlen, bytes_read); - ASSERT_EQUAL(ret.value(), huge_file::OKAY, "should be able to read file"); - if (ret == huge_file::OKAY) { - posn += bytes_read; - } - } - ASSERT_TRUE(test.eof(), "eof check should be at eof."); - if (posn != len) - log(a_sprintf("failed check, want %.0f, got %.0f", double(len), double(posn))); - ASSERT_EQUAL(posn, len, "eof check should be at right position: "); -// log(astring("successfully read ") + curr); - } -} - -int test_huge_file::execute() -{ - FUNCDEF("execute"); - run_file_scan(); - return final_report(); -} - -HOOPLE_MAIN(test_huge_file, ) - diff --git a/core/library/tests_mathematics/makefile b/core/library/tests_mathematics/makefile deleted file mode 100644 index bef10eb2..00000000 --- a/core/library/tests_mathematics/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_mathematics -TYPE = test -TARGETS = test_chaos.exe test_double_plus.exe test_math_ops.exe -DEFINITIONS += USE_HOOPLE_DLLS -LOCAL_LIBS_USED = unit_test application configuration filesystem loggers mathematics nodes \ - structures processes textual timely structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_mathematics/test_chaos.cpp b/core/library/tests_mathematics/test_chaos.cpp deleted file mode 100644 index 7be780f4..00000000 --- a/core/library/tests_mathematics/test_chaos.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* -* Name : test_chaos -* Author : Chris Koeritz -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -//#define DEBUG_CHAOS - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define MAX_RANDOM_BINS 40 -#define MAX_TEST_CYCLES 10008 -#define AVG_EXPECTED_PER_BIN (double(MAX_TEST_CYCLES) / double(MAX_RANDOM_BINS)) -#define VARIATION_ALLOWED (AVG_EXPECTED_PER_BIN * 0.1) -#define ANOMALIES_ALLOWED (MAX_RANDOM_BINS / 4) - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -class test_chaos : virtual public unit_base, virtual public application_shell -{ -public: - test_chaos() : application_shell() {} - DEFINE_CLASS_NAME("test_chaos"); - virtual int execute(); -}; - -int test_chaos::execute() -{ - FUNCDEF("execute"); -#ifdef DEBUG_CHAOS - LOG(a_sprintf("average expected=%f, variation allowed=%f", - AVG_EXPECTED_PER_BIN, VARIATION_ALLOWED)); -#endif - int results[MAX_RANDOM_BINS]; - for (int k = 0; k < MAX_RANDOM_BINS; k++) results[k] = 0; - chaos randomizer; - - for (int i = 0; i < MAX_TEST_CYCLES; i++) { - // first test if exclusivity is ensured... - int res = randomizer.exclusive(0, MAX_RANDOM_BINS - 1); - ASSERT_FALSE( (res <= 0) || (res >= MAX_RANDOM_BINS - 1), - "exclusive test should not go out of bounds"); - // then test for our statistics. - int base = randomizer.inclusive(-1000, 1000); - // pick a base for the number below. - res = randomizer.inclusive(base, base + MAX_RANDOM_BINS - 1); - ASSERT_FALSE( (res < base) || (res > base + MAX_RANDOM_BINS - 1), - "inclusive test should not go out of bounds"); -//LOG(a_sprintf("adding it to %d bin", res - base)); - results[res - base]++; - } -#ifdef DEBUG_CHAOS - LOG("Anomalies:"); -#endif - int failed_any = false; - for (int j = 0; j < MAX_RANDOM_BINS; j++) { - if (absolute_value(results[j] - AVG_EXPECTED_PER_BIN) > VARIATION_ALLOWED) { - failed_any++; -#ifdef DEBUG_CHAOS - LOG(astring(astring::SPRINTF, "%d: difference=%f", - j, double(results[j] - AVG_EXPECTED_PER_BIN))); -#endif - } - } -#ifdef DEBUG_CHAOS - if (!failed_any) LOG("None") - else LOG(a_sprintf("Saw %d anomalies of %d allowed.", failed_any, ANOMALIES_ALLOWED)); -#endif - - ASSERT_FALSE(failed_any > ANOMALIES_ALLOWED, - "probability anomalies should be less than the allowed number"); - return final_report(); -} - -HOOPLE_MAIN(test_chaos, ) - diff --git a/core/library/tests_mathematics/test_double_plus.cpp b/core/library/tests_mathematics/test_double_plus.cpp deleted file mode 100644 index df216d9c..00000000 --- a/core/library/tests_mathematics/test_double_plus.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* -* Name : test_double_plus -* Author : Chris Koeritz -* Purpose: Tests the double_plus class out. -** -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace geometric; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace unit_test; - -typedef double_plus floot; - -class test_double_plus : public virtual unit_base, public virtual application_shell -{ -public: - test_double_plus() : application_shell() {} - DEFINE_CLASS_NAME("test_double_plus"); - virtual int execute(); -}; - -int test_double_plus::execute() -{ - FUNCDEF("execute"); - floot x1 = 43.8106392325; - floot x2 = 43.8106; - ASSERT_EQUAL(x1, x2, "these doubles should be close enough"); - - floot y1 = 16.78; - floot y2 = 16.798273773; - ASSERT_INEQUAL(y1, y2, "these doubles shouldn't be close enough"); - - floot z1(16.8, 0.1); - floot z2(16.798273773, 0.1); - ASSERT_EQUAL(a_sprintf("%.3f", z2.truncate()), astring("16.800"), - "truncate should calculate proper string"); - ASSERT_EQUAL(z1, z2, "these doubles should be close enough with short delta"); - - floot q1(16.75, 0.01); - floot q2(16.749273773, 0.01); - ASSERT_EQUAL(a_sprintf("%.3f", q2.truncate()), astring("16.750"), - "wider truncate should calculate proper string"); - ASSERT_EQUAL(q1, q2, "next couple doubles should be close enough with small delta"); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_double_plus, ) - diff --git a/core/library/tests_mathematics/test_math_ops.cpp b/core/library/tests_mathematics/test_math_ops.cpp deleted file mode 100644 index 19f966a1..00000000 --- a/core/library/tests_mathematics/test_math_ops.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_math_ops * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -//using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -class test_math_ops : virtual public unit_base, virtual public application_shell -{ -public: - test_math_ops() {} - DEFINE_CLASS_NAME("test_math_ops"); - virtual int execute(); -}; - -////////////// - -int test_math_ops::execute() -{ - FUNCDEF("execute"); - // test one: make sure factorial is working. - basis::un_int fact3 = math_ops::factorial(3); - ASSERT_EQUAL(fact3, 6, "3! did not equal 6"); - basis::un_int fact8 = math_ops::factorial(8); - ASSERT_EQUAL(fact8, 40320, "8! did not equal 40320"); - basis::un_int fact10 = math_ops::factorial(10); - ASSERT_EQUAL(fact10, 3628800, "10! did not equal 3628800"); - - return final_report(); -} - -HOOPLE_MAIN(test_math_ops, ) - diff --git a/core/library/tests_nodes/makefile b/core/library/tests_nodes/makefile deleted file mode 100644 index 30ddb492..00000000 --- a/core/library/tests_nodes/makefile +++ /dev/null @@ -1,11 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_node -TYPE = test -TARGETS = test_list.exe test_node.exe test_packable_tree.exe test_symbol_tree.exe test_tree.exe -LOCAL_LIBS_USED = unit_test application nodes loggers processes filesystem configuration timely textual structures basis -DEFINITIONS += USE_HOOPLE_DLLS -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_nodes/test_list.cpp b/core/library/tests_nodes/test_list.cpp deleted file mode 100644 index 14a4fc7b..00000000 --- a/core/library/tests_nodes/test_list.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_list * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace mathematics; -using namespace nodes; -using namespace structures; -using namespace unit_test; - -//#define DEBUG_LIST - // uncomment this line to get more debugging output. - -const int DEFAULT_ITERATIONS = 50; - // the default number of times we run through our phase loop. - -typedef basket t_node; - // the object we store in the list, a templated integer. - -#define CASTER(bare_node) static_cast(bare_node) - // turns a node pointer into our special t_node. - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) - -////////////// - -class test_list : virtual public unit_base, virtual public application_shell -{ -public: - test_list() : unit_base() {} - DEFINE_CLASS_NAME("test_list"); - virtual int execute(); -}; - -HOOPLE_MAIN(test_list, ); - -////////////// - -int test_list::execute() -{ - FUNCDEF("execute"); - - list the_list; - chaos randomizer; - - int iterations_left = DEFAULT_ITERATIONS; - while (iterations_left-- > 0) { - - // run through the phases below as many times as we are told. - - { - // phase for adding a random number into the list. - int to_add = randomizer.inclusive(0, 100000); - - // seek the correct insertion place to keep the list ordered. - list::iterator iter = the_list.head(); - while (!iter.is_tail() && iter.observe() - && (CASTER(iter.observe())->stored() <= to_add) ) - iter++; - the_list.insert(iter, new t_node(2, to_add)); - } - - { - // test the list invariant (which is that all elements should be sorted - // in non-decreasing order). - list::iterator iter = the_list.tail(); - // initialize our comparator. - int bigger = CASTER(iter.observe())->stored(); - // loop backwards until we hit the head. - while (!iter.is_head()) { - // check that the last value is not less than the current value. - ASSERT_FALSE(bigger < CASTER(iter.observe())->stored(), - "invariant check should not find a mal-ordering in the list"); - bigger = CASTER(iter.observe())->stored(); - iter--; - } - } - - { - // if the conditions are favorable, we whack at least one element out of - // the list. - if (randomizer.inclusive(1, 100) < 20) { - int elem = the_list.elements(); - int to_whack = randomizer.inclusive(0, elem - 1); - - // start at the head of the list... - list::iterator iter = the_list.head(); - // and jump to the element we chose. - the_list.forward(iter, to_whack); - ASSERT_EQUAL(the_list.index(iter), to_whack, - "forward should not see logic error where index of element to zap is incorrect"); - ASSERT_FALSE(iter.is_tail(), - "forward should not see logic error where we get to the tail somehow"); - the_list.zap(iter); - } - } - - } - -#ifdef DEBUG_LIST - list::iterator iter = the_list.head(); - log(astring("")); - log(astring("list contents:")); - int indy = 0; - while (!iter.is_tail()) { - int item = CASTER(iter.observe())->stored(); - log(a_sprintf("item #%d: %d", indy, item)); - indy++; - iter++; - } -#endif - - return final_report(); -} - - diff --git a/core/library/tests_nodes/test_node.cpp b/core/library/tests_nodes/test_node.cpp deleted file mode 100644 index 82dfb77c..00000000 --- a/core/library/tests_nodes/test_node.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/*****************************************************************************\ -* * -* Name : t_node * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Tests out the node base class. * -* * -******************************************************************************* -* Copyright (c) 1989-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//hmmm: make this a more aggressive and realistic test. try implementing -// some list algorithms or graph algorithms to push node around. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace nodes; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -class test_node : public virtual unit_base, public virtual application_shell -{ -public: - test_node() {} - DEFINE_CLASS_NAME("test_node"); - virtual int execute(); -}; - -void bogon(byte_array *fred) -{ - if (fred) LOG("eep") - else LOG("eek"); -} - -int test_node::execute() -{ - FUNCDEF("execute"); - - byte_array blank; - basket fred(2, blank); - basket george(2, blank); - basket f_end1(0); - basket f_end2(0); - basket g_end1(0); - basket g_end2(0); - - node root; - - // add some links to the linkless root. - root.insert_link(0, &fred); - root.insert_link(1, &george); - - // set the pre-existing links to our end points. - fred.set_link(0, &f_end1); - fred.set_link(1, &f_end2); - george.set_link(0, &g_end1); - george.set_link(1, &g_end2); - - return final_report(); -} - -HOOPLE_MAIN(test_node, ); - diff --git a/core/library/tests_nodes/test_packable_tree.cpp b/core/library/tests_nodes/test_packable_tree.cpp deleted file mode 100644 index 3d971dda..00000000 --- a/core/library/tests_nodes/test_packable_tree.cpp +++ /dev/null @@ -1,204 +0,0 @@ -////////////// -// Name : test_packable_tree -// Author : Chris Koeritz -////////////// -// Copyright (c) 1992-$now By Author. 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: -// http://www.gnu.org/licenses/gpl.html -// or under the terms of the GNU Library license: -// http://www.gnu.org/licenses/lgpl.html -// at your preference. Those licenses describe your legal rights to this -// software, and no other rights or warranties apply. -// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. -////////////// - -//! tests some critical properties for the packable tree. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace nodes; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_PACKABLE_TREE - // set this to enable debugging features of the string class. - -//HOOPLE_STARTUP_CODE; - -//#define DEBUG_PACKABLE_TREE_TEST - // uncomment for testing version. - -#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) - -#define WHERE __WHERE__.s() - -#define FUNKIT(str) basis::a_sprintf("%s: %s", func, basis::astring(str).s()) - -// test: reports an error if the condition evaluates to non-zero. -int compnum = 0; -const float TEST_RUNTIME_DEFAULT = .02 * MINUTE_ms; - // the test, by default, will run for this long. - -////////////// - -class test_packable_tree : public application_shell, public unit_base -{ -public: - test_packable_tree() {} - ~test_packable_tree() {} - - DEFINE_CLASS_NAME("test_packable_tree"); - - virtual int execute() { - run_test(); - return final_report(); - } - - void run_test(); -}; - -HOOPLE_MAIN(test_packable_tree, ) - -////////////// - -//! it's not the one tree (c). this is just a derived packable_tree we can test with. - -class many_tree : public packable_tree -{ -public: - many_tree(const file_info &inf) : c_inf(new file_info(inf)) {} - - virtual ~many_tree() { WHACK(c_inf); } - - file_info get_info() const { return *c_inf; } - - virtual int packed_size() const { - return c_inf->packed_size(); - } - - virtual void pack(basis::byte_array &packed_form) const { - c_inf->pack(packed_form); - } - - virtual bool unpack(basis::byte_array &packed_form) { - if (!c_inf->unpack(packed_form)) return false; -//other pieces? - return true; - } - -private: - file_info *c_inf; -}; - -////////////// - -//! the factory that creates our special type of tree. - -class tree_defacto : public packable_tree_factory -{ -public: - packable_tree *create() { return new many_tree(file_info()); } -}; - -////////////// - -void test_packable_tree::run_test() -{ - FUNCDEF("run_test"); - - const file_info farfle(filename("arf"), 2010); - const file_info empty; - const file_info snood(filename("wookie"), 8888); - - { - // simple creation, packing, unpacking, destruction tests on a blank object. - many_tree gruntcake(farfle); - byte_array packed_form; - int pack_guess = gruntcake.packed_size(); - gruntcake.pack(packed_form); - ASSERT_EQUAL(pack_guess, packed_form.length(), FUNKIT("packed length is incorrect")); - many_tree untbake_target(empty); - ASSERT_TRUE(untbake_target.unpack(packed_form), FUNKIT("unpack operation failed")); - ASSERT_EQUAL(untbake_target.get_info(), gruntcake.get_info(), - FUNKIT("unpack had wrong contents")); - } - - { - // recursive packing tests... - // first layer. - many_tree *spork = new many_tree(farfle); - many_tree *limpet = new many_tree(empty); - many_tree *congo = new many_tree(snood); - many_tree *dworkin = new many_tree(empty); - many_tree *greep = new many_tree(farfle); - // second layer. - many_tree *flep = new many_tree(snood); - many_tree *glug = new many_tree(empty); - many_tree *aptitoot = new many_tree(farfle); - // third layer. - many_tree *grog = new many_tree(snood); - // connect first to second. - flep->attach(spork); - flep->attach(limpet); - glug->attach(congo); - aptitoot->attach(dworkin); - aptitoot->attach(greep); - // connect second to third. - grog->attach(flep); - grog->attach(glug); - grog->attach(aptitoot); - - // now recursively pack that bad boy three level tree. - byte_array packed; - int size_guess = grog->recursive_packed_size(); - grog->recursive_pack(packed); - ASSERT_EQUAL(size_guess, packed.length(), "recursive_packed_size failed"); - tree_defacto factotum; - packable_tree *unpacked = many_tree::recursive_unpack(packed, factotum); - ASSERT_TRUE(unpacked, "recursive_unpack failed"); - ASSERT_TRUE(dynamic_cast(unpacked), "recursive_unpack has wrong type"); - many_tree *survivor = dynamic_cast(unpacked); - -if (survivor) { -} - -//compare trees? - - } - -} - -////////////// - diff --git a/core/library/tests_nodes/test_symbol_tree.cpp b/core/library/tests_nodes/test_symbol_tree.cpp deleted file mode 100644 index ef9917c4..00000000 --- a/core/library/tests_nodes/test_symbol_tree.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_symbol_tree * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Creates a symbol_tree and performs some operations on it to assure * -* basic functionality. * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#include -//#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace nodes; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -#define DEBUG_SYMBOL_TREE - -class test_symbol_tree : public virtual unit_base, virtual public application_shell -{ -public: - test_symbol_tree() {} - DEFINE_CLASS_NAME("test_symbol_tree"); - int execute(); -}; - -int test_symbol_tree::execute() -{ - LOG("please check memory usage and record it, then hit a key to start testing."); - - try { - symbol_tree t("blork"); - symbol_tree *curr = &t; - for (int i = 0; i < 40000; i++) { - // if the current node has any branches, we'll jump on one as the next - // place. - if (curr->branches()) { - // move to a random branch. - int which = randomizer().inclusive(0, curr->branches() - 1); - curr = (symbol_tree *)curr->branch(which); - } - astring rando = string_manipulation::make_random_name(1, 10); - curr->add(new symbol_tree(rando)); - } - LOG("check memory usage now with full size. then hit a key."); - } catch (...) { - LOG("crashed during tree stuffing."); - return 1; - } - - LOG("check memory usage after the run. then hit a key to end " - "the program."); - -//create a tree structure... -//perform known operations and validate shape of tree. - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_symbol_tree, ) - diff --git a/core/library/tests_nodes/test_tree.cpp b/core/library/tests_nodes/test_tree.cpp deleted file mode 100644 index 5d3516ce..00000000 --- a/core/library/tests_nodes/test_tree.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* -* Name : test_tree * -* Author : Chris Koeritz * -* Purpose: * -* Tests out the tree class. * -** -* Copyright (c) 1993-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace nodes; -using namespace loggers; -using namespace structures; -using namespace unit_test; - -//#define DEBUG_TEST_TREE - // uncomment if you want the noisy streams version. - -const int test_iterations = 20; - -class bogotre : public packable, public tree -{ -public: - bogotre(const char *start = NIL) : who_cares(42), i_sure_dont('l'), - another_useless_int(23) { - astring to_init(start); - if (to_init.length() < 1) to_init += "ack"; - to_init.stuff(the_actual_string, minimum(to_init.length()+1, 500)); - } - DEFINE_CLASS_NAME("bogotre"); - virtual ~bogotre() {} - virtual void pack(byte_array &packed_form) const; - virtual bool unpack(byte_array &to_unpack); - virtual int packed_size() const; - virtual abyte *held() const { return (abyte *)the_actual_string; } - virtual void print() const { -#ifdef DEBUG_TEST_TREE - printf(the_actual_string); -#endif - } - -private: - char the_actual_string[500]; - int who_cares; - char i_sure_dont; - int another_useless_int; -}; - -////////////// - -// forward. -typedef bogotre larch; -typedef void (applier)(larch *apply_to); -typedef tree::iterator traveller; - -class test_tree : public virtual unit_base, virtual public application_shell -{ -public: - test_tree() : application_shell() {} - DEFINE_CLASS_NAME("test_tree"); - virtual int execute(); - static void print_node(larch *curr_node); - static larch *next(larch *&move, larch *hook, traveller &skip); - static void apply(larch *apply_to, applier *to_apply, - tree::traversal_directions order); -}; - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) - -int bogotre::packed_size() const -{ return strlen(the_actual_string) + 1 + sizeof(int) * 2 + sizeof(abyte); } - -void bogotre::pack(byte_array &packed_form) const -{ - FUNCDEF("pack"); - astring(the_actual_string).pack(packed_form); - structures::attach(packed_form, who_cares); - structures::attach(packed_form, i_sure_dont); - structures::attach(packed_form, another_useless_int); -} - -bool bogotre::unpack(byte_array &packed_form) -{ - FUNCDEF("unpack"); - // 5 is the magic knowledge of minimum packed string. -//hmmm: make the minimum packed size a property of packables? - ASSERT_FALSE(packed_form.length() < - int(1 + sizeof(who_cares) + sizeof(i_sure_dont) + sizeof(another_useless_int)), - "size of package should be correct"); - astring unpacked; - ASSERT_TRUE(unpacked.unpack(packed_form), "should be able to retrieve string"); - ASSERT_TRUE(structures::detach(packed_form, who_cares), "should retrieve who_cares"); - ASSERT_TRUE(structures::detach(packed_form, i_sure_dont), "should retrieve i_sure_dont"); - ASSERT_TRUE(structures::detach(packed_form, another_useless_int), - "should retrieve another_..."); - - ASSERT_EQUAL(who_cares, 42, "bogotre_unpack - right value held in first int"); - ASSERT_EQUAL(i_sure_dont, 'l', "bogotre_unpack - right character held"); - ASSERT_EQUAL(another_useless_int, 23, "bogotre_unpack - right value held in second int"); - return true; -} - -////////////// - -/* -bogotre *togen(char *to_store) -{ bogotre *to_return = new bogotre(astring(to_store).s()); return to_return; } -*/ - -void test_tree::print_node(larch *curr_node) -{ - FUNCDEF("print_node"); - ASSERT_TRUE(curr_node, "tree shouldn't be nil"); - bogotre *real_curr = dynamic_cast(curr_node); - ASSERT_TRUE(real_curr, "contents shouldn't be nil"); - astring to_examine((char *)real_curr->held()); -#ifdef DEBUG_TEST_TREE - to_examine += " "; - printf(to_examine.s()); -//remove it again if we reenable the cut. -#endif -// if (to_examine == to_look_for) real_curr->cut(); -} - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*this) - -////////////// - -larch *test_tree::next(larch *&move, larch *formal(hook), traveller &skip) -{ move = dynamic_cast(skip.next()); return move; } - -void test_tree::apply(larch *apply_to, applier *to_apply, - tree::traversal_directions order) -{ - larch *curr = NIL; - for (traveller skippy = apply_to->start(order); - next(curr, apply_to, skippy); ) to_apply(curr); -} - -int test_tree::execute() -{ - FUNCDEF("execute"); - for (int qq = 0; qq < test_iterations; qq++) { - larch *e1 = new larch("a"); - larch *e2 = new larch("b"); - larch *e3 = new larch("+"); - e3->attach(e1); - e3->attach(e2); - - larch *e4 = new larch("c"); - larch *e5 = new larch("-"); - e5->attach(e3); - e5->attach(e4); - - larch *e6 = new larch(">"); - larch *e7 = new larch("23"); - e6->attach(e5); - e6->attach(e7); - - larch *e8 = new larch("d"); - larch *e9 = new larch("="); - e9->attach(e8); - e9->attach(e6); - -#ifdef DEBUG_TEST_TREE - printf("infix is "); -#endif - apply(e9, print_node, tree::infix); -#ifdef DEBUG_TEST_TREE - printf("\nprefix is "); -#endif - apply(e9, print_node, tree::prefix); -#ifdef DEBUG_TEST_TREE - printf("\npostfix is "); -#endif - apply(e9, print_node, tree::postfix); -#ifdef DEBUG_TEST_TREE - printf("\n"); - printf("branches is "); -#endif - apply(e9, print_node, tree::to_branches); -#ifdef DEBUG_TEST_TREE - printf("\n"); - printf("branches reversed is "); -#endif - apply(e9, print_node, tree::reverse_branches); -#ifdef DEBUG_TEST_TREE - printf("\n"); - printf("before first pack"); -#endif - byte_array packed_e9(0); - int sizzle = e9->packed_size(); - e9->pack(packed_e9); - ASSERT_EQUAL(sizzle, packed_e9.length(), "packed size should agree with results"); -#ifdef DEBUG_TEST_TREE - printf("after first pack, size is %d\n", packed_e9.length()); -#endif - larch *new_e9 = new larch(); - new_e9->unpack(packed_e9); -#ifdef DEBUG_TEST_TREE - printf("New tree after unpacking is (infix order):\n"); -#endif - apply(new_e9, print_node, tree::infix); -#ifdef DEBUG_TEST_TREE - printf("\n"); -#endif -/* -#ifdef DEBUG_TEST_TREE - printf("the following dumps are in the order: infix, prefix, postfix.\n\n"); - printf("now trying cut on the character '>':\n"); -#endif - to_look_for = ">"; - new_e9->apply(&print_node, tree::infix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - new_e9->apply(&print_node, tree::prefix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - new_e9->apply(&print_node, tree::postfix); -#ifdef DEBUG_TEST_TREE - p("\nnow trying cut on the character +:\n"); -#endif - to_look_for = "+"; - new_e9->apply(&print_node, tree::infix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - new_e9->apply(&print_node, tree::prefix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - new_e9->apply(&print_node, tree::postfix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - to_look_for = ""; - -#ifdef DEBUG_TEST_TREE - p("okay, trying to resume at -\n"); -#endif - e5->resume(&print_node, tree::infix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - e5->resume(&print_node, tree::prefix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif - e5->resume(&print_node, tree::postfix); -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif -*/ -#ifdef DEBUG_TEST_TREE - printf("deleting\n"); -#endif - delete e9; -/* -printf("second pack\n"); - byte_array second_pack; -printf("packing\n"); - new_e9->pack(second_pack); -#ifdef DEBUG_TEST_TREE - printf("after second pack, size is %d\n", size); -#endif -*/ - delete new_e9; -/* - larch *newest_e9 = new larch(SELF_CLEANING); - newest_e9->unpack(second_pack); -#ifdef DEBUG_TEST_TREE - printf("after second unpack... tree is (infix):\n"); -#endif - newest_e9->apply(print_node, tree::infix); - delete newest_e9; -#ifdef DEBUG_TEST_TREE - p("\n"); -#endif -*/ - } - return final_report(); -} - -HOOPLE_MAIN(test_tree, ) - diff --git a/core/library/tests_structures/bogon.cpp b/core/library/tests_structures/bogon.cpp deleted file mode 100644 index 4bd88123..00000000 --- a/core/library/tests_structures/bogon.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/*****************************************************************************\ -* * -* Name : bogon * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* A simple test object for amorphs. * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "bogon.h" - -#include - -using namespace basis; -using namespace structures; - -bogon::bogon(abyte *to_copy) : my_held(NIL) -{ - if (to_copy) { - astring t((char *)to_copy); - if (t.length()) { - my_held = new abyte[t.length() + 1]; - t.stuff((char *)my_held, t.length() + 1); - } - } -} - -bogon::bogon(const bogon &to_copy) : my_held(NIL) { operator = (to_copy); } - -bogon &bogon::operator = (const bogon &to_copy) { - if (this == &to_copy) return *this; - astring t((char *)to_copy.my_held); - if (my_held) delete [] my_held; - my_held = new abyte[t.length() + 1]; - t.stuff((char *)my_held, t.length() + 1); - return *this; -} - -bogon::~bogon() { if (my_held) delete [] my_held; } - -abyte *bogon::held() const { return my_held; } - -int bogon::size() const { return my_held? int(strlen((char *)my_held) + 1) : 0; } - diff --git a/core/library/tests_structures/bogon.h b/core/library/tests_structures/bogon.h deleted file mode 100644 index 77edfa59..00000000 --- a/core/library/tests_structures/bogon.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef BOGON_CLASS -#define BOGON_CLASS - -/*****************************************************************************\ -* * -* Name : bogon * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* A simple test object for amorphs. * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#define DEBUG_ARRAY -#define DEBUG_AMORPH - -#include -#include -#include - -class bogon -{ -public: - bogon(basis::abyte *to_copy); - - bogon(const bogon &to_copy); - - bogon &operator = (const bogon &to_copy); - - ~bogon(); - - basis::abyte *held() const; - - int size() const; - -private: - basis::abyte *my_held; -}; - -#endif - diff --git a/core/library/tests_structures/makefile b/core/library/tests_structures/makefile deleted file mode 100644 index eb038004..00000000 --- a/core/library/tests_structures/makefile +++ /dev/null @@ -1,14 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_structures -TYPE = test -SOURCE = bogon.cpp -TARGETS = test_amorph.exe test_hash_table.exe test_int_hash.exe test_matrix.exe \ - test_memory_limiter.exe test_packing.exe test_stack.exe test_unique_id.exe \ - test_bit_vector.exe test_set.exe test_string_table.exe test_symbol_table.exe \ - test_version.exe -LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ - structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def diff --git a/core/library/tests_structures/test_amorph.cpp b/core/library/tests_structures/test_amorph.cpp deleted file mode 100644 index e5dfb1ca..00000000 --- a/core/library/tests_structures/test_amorph.cpp +++ /dev/null @@ -1,536 +0,0 @@ -/* -* Name : test_byte_array_amorph -* Author : Chris Koeritz -* Purpose: -* Puts the amorph object through its paces. -** -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "bogon.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define DEBUG_ARRAY - // uncomment to enable array debugging. - -#define DEBUG_AMORPH - // uncomment to enable amorph debugging. - -//#define DEBUG_TEST_AMORPH - // uncomment for this program to be noisier. - -#ifdef DEBUG_TEST_AMORPH - #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) -#else - #define LOG(to_print) {} -#endif - -////////////// - -class t_amorph : virtual public unit_base, virtual public application_shell -{ -public: - t_amorph() : unit_base() {} - DEFINE_CLASS_NAME("t_amorph"); - int test_bogon_amorph(); - int test_byte_array_amorph(); - byte_array fake_pack(amorph &me); - int compare(amorph &one, amorph &two); - amorph *fake_amorph_unpack(byte_array &packed_amorph); - int compare(const amorph &one, const amorph &two); - - struct blob_hold { int size; int offset; }; - - virtual int execute(); -}; - -#define PACK_BLOB_SIZE(max_limbs) (max_limbs * sizeof(blob_hold)) - -HOOPLE_MAIN(t_amorph, ); - -////////////// - -const int default_test_iterations = 2; - -const int MAX_LIMBS = 200; - // the highest number of items stored in the amorphs here. - -const int MIN_CHUBBY = 60; - // the smallest chunk to allocate for storing text strings... all strings - // must therefore be shorter than this length. -const int MAX_RANDO = 275; - // the maximum amount of space to add when allocating a randomly sized chunk. - -#define PROGRAM_NAME astring("test_amorph") - -int t_amorph::compare(amorph &one, amorph &two) -{ - FUNCDEF("compare amorph"); - ASSERT_EQUAL(one.elements(), two.elements(), "elements comparison"); - if (one.elements() != two.elements()) return false; - ASSERT_EQUAL(one.valid_fields(), two.valid_fields(), "valid fields comparison"); - if (one.valid_fields() != two.valid_fields()) return false; - for (int i = 0; i < one.elements(); i++) { - if (!one.get(i) && !two.get(i)) continue; - ASSERT_FALSE(!one.get(i) || !two.get(i), "inequal emptiness"); - ASSERT_EQUAL(one.get(i)->length(), two.get(i)->length(), "inequal sizes"); - if (one.get(i)->length() > 0) { - ASSERT_INEQUAL(one[i]->observe(), two[i]->observe(), "pointer in use twice"); - ASSERT_FALSE(memcmp(one[i]->observe(), two[i]->observe(), one[i]->length()), - "inequal contents"); - } - } - return true; -} - -byte_array t_amorph::fake_pack(amorph &me) -{ - FUNCDEF("fake_pack"); - // snagged from the packable_amorph pack function! - // count the whole size needed to store the amorph. - int amo_size = 0; - amorph hold_packed_bits(me.elements()); - - for (int i = 0; i < me.elements(); i++) - if (me.get(i) && me.get(i)->length()) { - byte_array packed_item; - attach(packed_item, *me[i]); - byte_array *to_stuff = new byte_array(packed_item); - hold_packed_bits.put(i, to_stuff); - amo_size += packed_item.length(); - } - int len = amo_size + sizeof(int) + PACK_BLOB_SIZE(me.elements()); - - // allocate a storage area for the packed form. - byte_array to_return(len); - int temp = me.elements(); - memcpy((int *)to_return.access(), &temp, sizeof(int)); - // size of package is stored at the beginning of the memory. - - int current_offset = sizeof(int); - // the indices into the packed form are located after the amorph header. - blob_hold *blob_array = (blob_hold *)(to_return.access() + current_offset); - current_offset += PACK_BLOB_SIZE(me.elements()); - - // the entire amorph is replicated into the new buffer. - for (int j = 0; j < me.elements(); j++) { - // the offset of this limb in the packed area is saved in the hold. - blob_array[j].size - = (hold_packed_bits[j]? hold_packed_bits[j]->length() : 0); - blob_array[j].offset = current_offset; - if (hold_packed_bits[j] && hold_packed_bits[j]->length()) { - // the actual data is copied.... - memcpy(to_return.access() + current_offset, - (abyte *)hold_packed_bits[j]->observe(), - hold_packed_bits[j]->length()); - // and the "address" is updated. - current_offset += hold_packed_bits[j]->length(); - } - } - ASSERT_EQUAL(current_offset, len, "offset is incorrect after packing"); - return to_return; -} - -amorph *t_amorph::fake_amorph_unpack(byte_array &packed_amorph) -{ - // snagged from the packable_amorph unpack function! - int max_limbs; - memcpy(&max_limbs, (int *)packed_amorph.access(), sizeof(max_limbs)); - amorph *to_return = new amorph(max_limbs); - - blob_hold *blob_array = new blob_hold[max_limbs]; - memcpy(blob_array, (blob_hold *)(packed_amorph.access() - + sizeof(int)), PACK_BLOB_SIZE(max_limbs)); - for (int i = 0; i < to_return->elements(); i++) - if (blob_array[i].size) { - abyte *source = packed_amorph.access() + blob_array[i].offset; - byte_array packed_byte_array(blob_array[i].size, source); - byte_array *unpacked = new byte_array; - detach(packed_byte_array, *unpacked); - to_return->put(i, unpacked); - } - delete [] blob_array; - return to_return; -} - -int t_amorph::test_byte_array_amorph() -{ - FUNCDEF("test_byte_array_amorph"); - LOG("start of amorph of abyte array test"); - for (int qq = 0; qq < default_test_iterations; qq++) { - LOG(astring(astring::SPRINTF, "index %d", qq)); - { - // some simple creation and stuffing tests.... - amorph fred(20); - amorph gen(10); - for (int i=0; i < 10; i++) { - byte_array *gens = new byte_array(8, (abyte *)"goodbye"); - gen.put(i, gens); - } - for (int j = 0; j < 20; j++) { - byte_array *freds = new byte_array(6, (abyte *)"hello"); - fred.put(j, freds); - } - amorph_assign(gen, fred); - LOG("done with fred & gen"); - } - - LOG("before fred creation"); - chaos randomizer; - amorph fred(MAX_LIMBS - 1); - fred.append(NIL); // add one to make it max limbs big. - LOG("after append nil"); - { - for (int i = 0; i < fred.elements(); i++) { - int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); - astring text("bogus burfonium nuggets"); - astring burph(astring::SPRINTF, " ung %d ", i); - text += burph; - abyte *temp = new abyte[size]; - text.stuff((char *)temp, text.length()+1); - byte_array *to_stuff = new byte_array(size, temp); - fred.put(i, to_stuff); - delete [] temp; - } - } - LOG("after first loop"); - { - amorph bungee3; - amorph_assign(bungee3, fred); - amorph burglar2; - amorph_assign(burglar2, bungee3); - amorph trunklid; - amorph_assign(trunklid, burglar2); - ASSERT_INEQUAL(trunklid.elements(), 0, "const constructor test - no elements!"); - } - LOG("after copies performed"); - { - astring text; - text = "hello this is part one."; - LOG(astring(astring::SPRINTF, "len is %d, content is %s", - text.length(), text.observe())); - char *tadr = text.access(); - abyte *badr = (abyte *)tadr; - byte_array *to_stuff = new byte_array(text.length() + 1, badr); - fred.put(183, to_stuff); - text = "wonky tuniea bellowbop"; - byte_array *to_stuff1 = new byte_array(text.length()+1, (abyte *)text.s()); - fred.put(90, to_stuff1); - - text = "frunkwioioio"; - byte_array *to_stuff2 = new byte_array(text.length()+1, (abyte *)text.s()); - fred.put(12, to_stuff2); - - fred.clear(98); fred.clear(122); fred.clear(123); - fred.clear(256); - fred.clear(129); - fred.zap(82, 90); - fred.zap(93, 107); - } - LOG("after second loop"); - { - byte_array packed = fake_pack(fred); - LOG(astring(astring::SPRINTF, "done packing in %s, pack has %d " - "elems.", class_name(), packed.length())); - amorph *new_fred = fake_amorph_unpack(packed); - LOG("done unpacking in test_amorph"); - ASSERT_TRUE(compare(fred, *new_fred), "first pack test, amorphs not the same"); - abyte *cont1 - = (new_fred->get(14)? (*new_fred)[14]->access() : (abyte *)"NIL"); - abyte *cont2 - = (new_fred->get(20)? (*new_fred)[20]->access() : (abyte *)"NIL"); - abyte *cont3 - = (new_fred->get(36)? (*new_fred)[36]->access() : (abyte *)"NIL"); - - if (cont1) LOG(astring(astring::SPRINTF, "14: %s", cont1)); - if (cont2) LOG(astring(astring::SPRINTF, "20: %s", cont2)); - if (cont3) LOG(astring(astring::SPRINTF, "36: %s", cont3)); - LOG("fields all compare identically after pack and unpack"); - byte_array packed_second = fake_pack(*new_fred); - delete new_fred; - amorph *newer_fred = fake_amorph_unpack(packed_second); - ASSERT_TRUE(compare(*newer_fred, fred), "second pack test, amorphs not the same"); - delete newer_fred; - } - - { - amorph fred(randomizer.inclusive(20, 30)); - int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); - astring text("bogus burfonium nuggets"); - astring burph(astring::SPRINTF, " ung %d ", 2314); - text += burph; - byte_array intermed(size); - - for (int i = 0; i < fred.elements(); i += 5) { - byte_array *to_stuff = new byte_array(size, intermed.access()); - memcpy(intermed.access(), (abyte *)text.s(), text.length() + 1); - fred.put(i, to_stuff); - } - fred.clear_all(); - for (int j = 0; j < fred.elements(); j += 5) { - byte_array *to_stuff = new byte_array(size, intermed.access()); - memcpy(intermed.access(), (abyte *)text.s(), text.length() + 1); - fred.put(j, to_stuff); - } - text = "frunkwioioio"; - byte_array *to_stuff = new byte_array(text.length()+1, (abyte *)text.s()); - fred.put(12, to_stuff); - fred.clear_all(); - } - LOG("survived the clear_alls"); - { - amorph *ted = new amorph(0); - amorph_assign(*ted, fred); - ASSERT_TRUE(compare(*ted, fred), "ted and fred aren't the same"); - { - amorph *george = new amorph(0); - amorph_assign(*george, fred); - ASSERT_TRUE(compare(*george, fred), "fred and george aren't the same"); - ted->zap(3, 20); - george->zap(3, 10); - george->zap(3, 12); - ASSERT_TRUE(compare(*ted, *george), "after zap, ted and george aren't the same"); - ted->adjust(ted->elements() - 20); - george->adjust(george->elements() - 5); - george->adjust(george->elements() - 5); - george->adjust(george->elements() - 5); - george->adjust(george->elements() - 5); - ASSERT_TRUE(compare(*ted, *george), "after adjust, ted and george aren't the same"); - delete george; - } - delete ted; - } - } - return 0; -} - -int t_amorph::compare(const amorph &one, const amorph &two) -{ - FUNCDEF("compare amorph"); - if (one.elements() != two.elements()) return false; - for (int i = 0; i < one.elements(); i++) { - if (!one.get(i) && !two.get(i)) continue; - ASSERT_FALSE(!one.get(i) || !two.get(i), "both should be non-nil"); - ASSERT_EQUAL(one.get(i)->size(), two.get(i)->size(), "sizes should be equal"); - if (one.get(i)->size() > 0) { - ASSERT_INEQUAL(one.get(i)->held(), two.get(i)->held(), "pointer should not be in use twice"); - ASSERT_FALSE(memcmp(one.get(i)->held(), two.get(i)->held(), one.get(i)->size()), - "contents should be equal"); - } - } - return true; -} - -int t_amorph::test_bogon_amorph() -{ - FUNCDEF("test_bogon_amorph"); - LOG("start of amorph of bogon test"); - for (int qq = 0; qq < default_test_iterations; qq++) { - LOG(astring(astring::SPRINTF, "index %d", qq)); - { - // some simple creation and stuffing tests.... - amorph fred(20); - amorph gen(10); - for (int i = 0; i < 10; i++) { - bogon *gens = new bogon((abyte *)"goodbye"); - gen.put(i, gens); - } - for (int j = 0; j < 20; j++) { - bogon *freds = new bogon((abyte *)"hello"); - fred.put(j, freds); - } - ASSERT_FALSE(compare(fred, gen), "fred and gen ARE the same"); - amorph_assign(gen, fred); - ASSERT_TRUE(compare(fred, gen), "fred and gen aren't the same"); - } - - chaos randomizer; - - amorph fred(MAX_LIMBS); - - LOG("after append nil"); - { - for (int i = 0; i < fred.elements(); i++) { - int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); - astring text("bogus burfonium nuggets"); - astring burph(astring::SPRINTF, " ung %d ", i); - text += burph; - abyte *temp = new abyte[size]; - text.stuff((char *)temp, text.length()+1); - bogon *to_stuff = new bogon(temp); - fred.put(i, to_stuff); - delete [] temp; - } - } - - LOG("after first loop"); - { - amorph bungee3; - amorph_assign(bungee3, fred); - amorph burglar2; - amorph_assign(burglar2, bungee3); - amorph_assign(burglar2, bungee3); - amorph trunklid; - amorph_assign(trunklid, burglar2); - ASSERT_TRUE(trunklid.elements(), "const constructor test: no elements!"); - } - { - astring text; - text = "hello this is part one."; - bogon *to_stuff = new bogon((abyte *)text.s()); - fred.put(32, to_stuff); - - text = "wonky tuniea bellowbop"; - bogon *to_stuff1 = new bogon((abyte *)text.s()); - fred.put(84, to_stuff1); - - text = "frunkwioioio"; - bogon *to_stuff2 = new bogon((abyte *)text.s()); - fred.put(27, to_stuff2); - - fred.clear(98); fred.clear(122); fred.clear(123); - fred.clear(256); - fred.clear(129); - fred.zap(82, 90); - fred.zap(93, 107); - } - LOG("after second loop"); - { - amorph fred(randomizer.inclusive(20, 30)); - astring text("bogus burfonium nuggets"); - astring burph(astring::SPRINTF, " ung %d ", 2314); - text += burph; - - for (int i = 0; i < fred.elements(); i += 5) { - bogon *to_stuff = new bogon((abyte *)text.s()); - fred.put(i, to_stuff); - } - fred.clear_all(); - for (int j = 0; j < fred.elements(); j += 5) { - bogon *to_stuff = new bogon((abyte *)text.s()); - fred.put(j, to_stuff); - } - text = "frunkwioioio"; - bogon *to_stuff = new bogon((abyte *)text.s()); - fred.put(6, to_stuff); - fred.clear_all(); - } - LOG("survived the clear_alls"); - { - amorph *ted = new amorph(); - amorph_assign(*ted, fred); - ASSERT_TRUE(compare(*ted, fred), "after assign, ted and fred aren't the same"); - { - amorph *george = new amorph(); - amorph_assign(*george, fred); - ASSERT_TRUE(compare(*george, fred), "pre-zap, george and fred aren't the same"); - ted->zap(3, 20); - george->zap(3, 10); - george->zap(3, 12); - ASSERT_TRUE(compare(*ted, *george), "after zap, ted and george aren't the same"); - ted->adjust(ted->elements()-20); - george->adjust(george->elements()-5); - george->adjust(george->elements()-5); - george->adjust(george->elements()-5); - george->adjust(george->elements()-5); - ASSERT_TRUE(compare(*ted, *george), "after more zaps, ted and george aren't the same"); - delete george; - } - delete ted; - } - } - return 0; -} - -const int MAX_TEST_DURATION = 1 * MINUTE_ms; - // each of the tests calling on the templated tester will take this long. - -const int MAX_SIMULTANEOUS_OBJECTS = 42; // the maximum length tested. - -//hmmm: this test_amorph_of is not completed. - -template -int test_amorph_of(const contents &bogus) -{ - chaos rando; - - // these are the actions we try on the amorph during the test. - // the first and last elements must be identical to the first and last - // tests to perform. - enum actions { first, do_zap = first, do_adjust, do_assign, - - - do_borrow, last = do_borrow}; - - time_stamp exit_time(::MAX_TEST_DURATION); - while (time_stamp() < exit_time) { - int index = rando.inclusive(0, ::MAX_SIMULTANEOUS_OBJECTS - 1); - int choice = rando.inclusive(first, last); - switch (choice) { - case do_zap: { - - break; - } - case do_adjust: { - - break; - } - case do_assign: { - - break; - } - case do_borrow: { - - break; - } - } - } -} - -int t_amorph::execute() -{ - SETUP_COMBO_LOGGER; - int errs = 0; - int retval = test_byte_array_amorph(); - if (retval != 0) errs += retval; - retval = test_bogon_amorph(); - if (retval != 0) errs += retval; - -//incorporate these errors somehow also. - -// if (retval == 0) -// critical_events::alert_message("amorph:: works for those functions tested."); -// else -// critical_events::alert_message("amorph:: there were errors!"); - return final_report(); -} - diff --git a/core/library/tests_structures/test_bit_vector.cpp b/core/library/tests_structures/test_bit_vector.cpp deleted file mode 100644 index 068f8a81..00000000 --- a/core/library/tests_structures/test_bit_vector.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_bit_vector * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) - -#define MAX_TEST 100 -#define FOOP_MAX 213 - -////////////// - -class test_bit_vector : virtual public unit_base, virtual public application_shell -{ -public: - test_bit_vector() : unit_base() {} - DEFINE_CLASS_NAME("test_bit_vector"); - virtual int execute(); -}; - -HOOPLE_MAIN(test_bit_vector, ); - -////////////// - -struct test_struct { basis::un_int store; int posn; int size; }; - -int test_bit_vector::execute() -{ - FUNCDEF("execute"); - SETUP_COMBO_LOGGER; - - const array unused; - - chaos randomizer; - bit_vector foop(FOOP_MAX); - - for (int i = 0; i < MAX_TEST; i++) { - // sets a random bit and finds that one. - int rando = randomizer.inclusive(0, FOOP_MAX-1); - foop.light(rando); - int found = foop.find_first(true); - ASSERT_EQUAL(found, rando, "find first locates first true"); - foop.clear(rando); - - foop.resize(FOOP_MAX); - ASSERT_EQUAL(0, foop.find_first(0), "locating location of first zero"); - ASSERT_EQUAL(common::NOT_FOUND, foop.find_first(1), "showing there are no one bits"); - for (int i = 0; i < 12; i++) foop.light(i); - ASSERT_EQUAL(12, foop.find_first(0), "finding first on partially set vector"); - - foop.light(FOOP_MAX); // shouldn't work, but shouldn't die. - ASSERT_FALSE(foop.on(FOOP_MAX), "bit_on should not be lit past end of vector"); - - // sets a bunch of random bits. - for (int j = 0; j < 40; j++) { - int rando = randomizer.inclusive(0, FOOP_MAX-1); - foop.light(rando); - } - bit_vector foop2(FOOP_MAX, ((const byte_array &)foop).observe()); - ASSERT_EQUAL(foop, foop2, "after lighting, vectors should be identical"); - - { - // this block tests the subvector and int storage/retrieval routines. - if (foop.bits() < 90) foop.resize(90); // make sure we have room to play. - - array tests; - test_struct t1 = { 27, 15, 5 }; - tests += t1; - test_struct t2 = { 8, 25, 4 }; - tests += t2; - test_struct t3 = { 1485, 34, 16 }; - tests += t3; - test_struct t4 = { 872465, 50, 32 }; - tests += t4; - - for (int i = 0; i < tests.length(); i++) { - ASSERT_TRUE(foop.set(tests[i].posn, tests[i].size, tests[i].store), - "storing int in vector should work"); - -//hmmm: make this a test case! -// bit_vector found = foop.subvector(tests[i].posn, tests[i].posn+tests[i].size-1); -// LOG(astring(astring::SPRINTF, "contents found:\n%s", found.text_form().s())); - - basis::un_int to_check = foop.get(tests[i].posn, tests[i].size); - if (to_check != tests[i].store) - LOG(a_sprintf("int found at %d in vector (%u) is different than what was stored (%u).", - i, to_check, tests[i].store)); - ASSERT_EQUAL((int)to_check, (int)tests[i].store, "should see expected int stored in vector"); - } - } - - { - // tests random resizings and resettings. - int number_of_loops = randomizer.inclusive(50, 150); - for (int i = 0; i < number_of_loops; i++) { - int which_to_do = randomizer.inclusive(1, 3); - switch (which_to_do) { - case 1: { - // resize. - int new_size = randomizer.inclusive(0, 32000); - foop.resize(new_size); - break; - } - case 2: { - // reset. - int new_size = randomizer.inclusive(0, 32000); - foop.reset(new_size); - break; - } - case 3: { - // random sets. - int sets_to_do = randomizer.inclusive(40, 280); - for (int i = 0; i < sets_to_do; i++) { - int rando = randomizer.inclusive(0, foop.bits()); - if (randomizer.inclusive(0, 1)) foop.light(rando); - else foop.clear(rando); - } - break; - } - } - } - } - - foop.reset(FOOP_MAX); // to clear before next loop. - } - - return final_report(); -} - diff --git a/core/library/tests_structures/test_hash_table.cpp b/core/library/tests_structures/test_hash_table.cpp deleted file mode 100644 index 0e2e214c..00000000 --- a/core/library/tests_structures/test_hash_table.cpp +++ /dev/null @@ -1,540 +0,0 @@ -/* -* Name : test_hash_table -* Author : Chris Koeritz -* Purpose: -* Tests out the hash_table abstract data type. -** -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -///using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_HASH_TABLE - // uncomment for noisier run. - -const double TEST_DURATION = 0.014 * MINUTE_ms; -//const double TEST_DURATION = 20 * SECOND_ms; - -const int MAX_ELEMENTS = 8; - // we start low since we will rehash occasionally. - -////////////// - -enum test_actions { - FIRST_TEST = 38, // place-holder. - ADD = FIRST_TEST, - // adds an item that is probably new. - ADD_ADD, - // adds an item that is probably new, followed by another item under the - // same key id. this ensures the overwriting gets tested. - ZAP, - // finds an item we know is in the list and whacks it. - ADD_ZAP, - // adds a new item and immediately finds and zaps it. - ZAP_ADD, - // zaps an item that we know about and then adds a new item with the same - // identifier. - FIND, - // locates an item in the list which we know should exist. - ACQUIRE, - // grabs an item out of the list (and tosses it). - FIND_ZAP_ADD, - // finds an item we know should exist, zaps it out of the list, then adds - // a new item with the same id. - ACQUIRE_ADD_ZAP, - // removes an item from the list that we know should be there, adds it back - // in, and then whacks it. - FIND_ADD_FIND, - // find an item with a particular id (one that we know should be in the - // list) and then adds a different item using the same id. the new item - // is then sought. - RESET, - // tosses all data out of the hash table. not done very often. - CHECK_SANITY, - // look for any problems or irregularities; print the contents of the list - // if any are found. - REHASH, - // resizes the hash table. - COPY, - // copies a hash table to another hash table. - LAST_TEST = COPY // place-holder; must equal test just prior. -}; - -////////////// - -// a simple object that is used as the contents of the hash_table. - -class data_shuttle -{ -public: - int food_bar; - astring snacky_string; - bool hungry; - byte_array chunk; - chaos chao; - - data_shuttle() - : snacky_string(string_manipulation::make_random_name()), - chunk(chao.inclusive(100, 10000)) {} -}; - -////////////// - -class test_hash_table : virtual public unit_base, virtual public application_shell -{ -public: - test_hash_table(); - - DEFINE_CLASS_NAME("test_hash_table"); - - int raw_random_id(); //!< returns an unvetted random number. - int unused_random_id(); //!< returns an unused (so far) random number. - - int execute(); - // the main startup for the test. - - bool perform_a_test(test_actions test_type); - // carries out the specifics of the "test_type". - - bool pick_a_test(); - // randomly picks one of the test types and performs it. - - static const char *test_name(test_actions test_type); - - // these functions each perform one type of test, which their names indicate. - bool test_add(); - bool test_add_add(); - bool test_zap(); - bool test_add_zap(); - bool test_zap_add(); - bool test_find(); - bool test_acquire(); - bool test_find_zap_add(); - bool test_acquire_add_zap(); - bool test_find_add_find(); - bool test_reset(bool always_run = false); - bool test_check_sanity(); - bool test_copy(); - bool test_rehash(); - - static bool equivalence_applier(const int &key, data_shuttle &item, void *dlink); - -private: - int_set _keys_in_use; // keys that we think are stored in the table. - hash_table _the_table; // our table under test. - int _hits[LAST_TEST - FIRST_TEST + 1]; // tracks our testing activities. - int _tested; // simple counter of number of test calls. -}; - -////////////// - -typedef hash_table our_hash; // cleans up somewhat. - -////////////// - -test_hash_table::test_hash_table() -: application_shell(), - _the_table(rotating_byte_hasher(), MAX_ELEMENTS), - _tested(0) -{ - for (int i = FIRST_TEST; i <= LAST_TEST; i++) - _hits[i - FIRST_TEST] = 0; -} - -int test_hash_table::raw_random_id() -{ - return randomizer().inclusive(-MAXINT32 / 4, MAXINT32 / 4); -} - -int test_hash_table::unused_random_id() -{ - while (true) { - int checking = raw_random_id(); - if (!_keys_in_use.member(checking)) return checking; // got one. - } // keep going until we find unused id. -} - -int test_hash_table::execute() -{ - time_stamp exit_time((int)TEST_DURATION); - while (time_stamp() < exit_time) { - pick_a_test(); - } - test_reset(true); // force it to run at least once. - -#ifdef DEBUG_HASH_TABLE - log(a_sprintf("did %d tests.\n", _tested)); - log(astring("Test Activity:")); - for (int i = 0; i < LAST_TEST - FIRST_TEST + 1; i++) - log(astring(astring::SPRINTF, "%d (%s): %d hits", i + FIRST_TEST, - test_name(test_actions(i + FIRST_TEST)), _hits[i])); - log(a_sprintf("note that test %d will seldom be executed.", RESET)); -#endif - return final_report(); -} - -const char *test_hash_table::test_name(test_actions test_type) -{ - switch (test_type) { - case ADD: return "ADD"; - case ADD_ADD: return "ADD_ADD"; - case ZAP: return "ZAP"; - case ADD_ZAP: return "ADD_ZAP"; - case ZAP_ADD: return "ZAP_ADD"; - case FIND: return "FIND"; - case ACQUIRE: return "ACQUIRE"; - case FIND_ZAP_ADD: return "FIND_ZAP_ADD"; - case ACQUIRE_ADD_ZAP: return "ACQUIRE_ADD_ZAP"; - case FIND_ADD_FIND: return "FIND_ADD_FIND"; - case RESET: return "RESET"; - case COPY: return "COPY"; - case REHASH: return "REHASH"; - case CHECK_SANITY: return "CHECK_SANITY"; - default: return "UnknownTest"; - } -} - -bool test_hash_table::perform_a_test(test_actions test_type) -{ - FUNCDEF("perform_a_test"); - -// log(astring(test_name(test_type)) + " "); - - switch (test_type) { - case ADD: return test_add(); - case ADD_ADD: return test_add_add(); - case ZAP: return test_zap(); - case ADD_ZAP: return test_add_zap(); - case ZAP_ADD: return test_zap_add(); - case FIND: return test_find(); - case ACQUIRE: return test_acquire(); - case FIND_ZAP_ADD: return test_find_zap_add(); - case ACQUIRE_ADD_ZAP: return test_acquire_add_zap(); - case FIND_ADD_FIND: return test_find_add_find(); - case RESET: return test_reset(); - case COPY: return test_copy(); - case REHASH: return test_rehash(); - case CHECK_SANITY: return test_check_sanity(); - default: - ASSERT_TRUE(false, "should not see any missing cases"); - return false; // never gets here. - } -} - -bool test_hash_table::pick_a_test() -{ - _tested++; - return perform_a_test(test_actions(randomizer().inclusive(FIRST_TEST, - LAST_TEST))); -} - -bool test_hash_table::test_add() -{ - FUNCDEF("test_add"); - _hits[ADD - FIRST_TEST]++; - int random_id = raw_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - outcome expected = common::IS_NEW; - if (_keys_in_use.member(random_id)) common::EXISTING; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), expected.value(), - "add should give proper outcome based on expectation"); - if (_keys_in_use.member(random_id)) - return true; // already was there so we replaced. - _keys_in_use.add(random_id); - return true; -} - -////////////// - -hash_table *_hang_on = NIL; - // must be set before calling the apply method. - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) - -bool test_hash_table::equivalence_applier(const int &key, data_shuttle &item, void *dlink) -{ - FUNCDEF("equivalence_applier"); - ASSERT_NON_NULL(dlink, "should have been given name"); - if (!dlink) return false; // fail. - astring test_name = (char *)dlink; - -//application_shell::single_instance()->log(astring("after name check")); - - data_shuttle *found = _hang_on->find(key); - ASSERT_NON_NULL(found, test_name + ": should find equivalent entry in second list"); - if (!found) return false; // bail or we'll crash. - -//application_shell::single_instance()->log(astring("after finding")); - - ASSERT_EQUAL(item.food_bar, found->food_bar, test_name + ": food_bar should not differ"); - ASSERT_EQUAL(item.snacky_string, found->snacky_string, test_name + ": snacky_string should not differ"); - ASSERT_EQUAL(item.hungry, found->hungry, test_name + ": hungry should not differ"); - return true; -} - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*this) - -////////////// - -bool test_hash_table::test_rehash() -{ - FUNCDEF("test_rehash"); - _hang_on = &_the_table; // must happen first. - - // we don't want to rehash too often; it is expensive. - int maybe = randomizer().inclusive(1, 50); - if (maybe < 32) return true; // not this time. - - _hits[REHASH - FIRST_TEST]++; - - hash_table table_copy(rotating_byte_hasher(), - _the_table.estimated_elements()); - -//log("copying table..."); - copy_hash_table(table_copy, _the_table); - // make a copy of the table. - -//log("rehashing table..."); - _the_table.rehash(randomizer().inclusive(1, 20)); -//hmmm: need test of non-existent dehash function that reduces max_bits. - -//log("comparing table..."); - table_copy.apply(equivalence_applier, (void*)func); -//log("done copy and compare."); - - return true; -} - -bool test_hash_table::test_copy() -{ - FUNCDEF("test_copy"); - _hang_on = &_the_table; // must happen first. - - // we don't want to copy too often. it's a heavy operation. - int maybe = randomizer().inclusive(1, 50); - if (maybe > 16) return true; // not this time. - - _hits[COPY - FIRST_TEST]++; - - hash_table table_copy(rotating_byte_hasher(), MAX_ELEMENTS); - -//log("copying table..."); - copy_hash_table(table_copy, _the_table); - // make a copy of the table. - -//log("comparing table..."); - table_copy.apply(equivalence_applier, (void*)func); -//log("done copy and compare."); - - return true; -} - -////////////// - -bool test_hash_table::test_add_add() -{ - FUNCDEF("test_add_add"); - _hits[ADD_ADD - FIRST_TEST]++; - int random_id = unused_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, - "new addition should be seen as such"); - // add the new key if it's really new. - _keys_in_use.add(random_id); - - // second add on same id. - data_shuttle *next_add = new data_shuttle; - next_add->snacky_string = string_manipulation::make_random_name(); - next_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, next_add).value(), our_hash::EXISTING, - "second add should not say first failed"); - - return true; -} - -bool test_hash_table::test_zap() -{ - FUNCDEF("test_zap"); - int maybe = randomizer().inclusive(1, 1000); - if (maybe > 50) return true; - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ZAP - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int dead_key = _keys_in_use[rand_indy]; - _keys_in_use.remove(dead_key); // remove the record of that key. - ASSERT_TRUE(_the_table.zap(dead_key), "key should be present in table"); - return true; -} - -bool test_hash_table::test_add_zap() -{ - FUNCDEF("test_add_zap"); - // add. - _hits[ADD_ZAP - FIRST_TEST]++; - int random_id = unused_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, - "putting new item in should be seen as new"); - // zap. - ASSERT_TRUE(_the_table.zap(random_id), "key should be present after add"); - return true; -} - -bool test_hash_table::test_zap_add() -{ - FUNCDEF("test_zap_add"); - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ZAP_ADD - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - // in the end, the key list state won't be changed unless the test fails. - int dead_key = _keys_in_use[rand_indy]; - ASSERT_TRUE(_the_table.zap(dead_key), "key should be there when we look"); - - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = dead_key; - outcome ret = _the_table.add(dead_key, to_add); - ASSERT_EQUAL(ret.value(), our_hash::IS_NEW, "key should not be present already"); - return true; -} - -bool test_hash_table::test_find() -{ - FUNCDEF("test_find"); - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[FIND - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int find_key = _keys_in_use[rand_indy]; - data_shuttle *found = NIL; - ASSERT_TRUE(_the_table.find(find_key, found), "key should be there as expected"); - ASSERT_NON_NULL(found, "contents should not be NIL"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should have length"); - return true; -} - -bool test_hash_table::test_acquire() -{ - FUNCDEF("test_acquire"); - int maybe = randomizer().inclusive(1, 1000); - if (maybe > 150) return true; - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ACQUIRE - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int find_key = _keys_in_use[rand_indy]; - _keys_in_use.remove(find_key); // remove the record of that key. - data_shuttle *found = _the_table.acquire(find_key); - ASSERT_NON_NULL(found, "key should be present when expected"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should not have zero length"); - WHACK(found); - found = _the_table.acquire(find_key); - ASSERT_NULL(found, "key should not be there after zap"); - return true; -} - -bool test_hash_table::test_find_zap_add() -{ - FUNCDEF("test_find_zap_add"); - // find. - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[FIND_ZAP_ADD - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - // this is another key list invariant function, if it works. - int find_key = _keys_in_use[rand_indy]; - data_shuttle *found = NIL; - ASSERT_TRUE(_the_table.find(find_key, found), "key should be locateable"); - ASSERT_NON_NULL(found, "key should not have NIL contents"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should be equal to real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should not have zero length"); - // zap. - ASSERT_TRUE(_the_table.zap(find_key), "should be able to zap the item we had found"); - // add. - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = find_key; - ASSERT_EQUAL(_the_table.add(find_key, to_add).value(), our_hash::IS_NEW, - "the item we zapped should be gone"); - return true; -} - -bool test_hash_table::test_reset(bool always_run) -{ - FUNCDEF("test_reset"); - if (!always_run) { - int maybe = randomizer().inclusive(1, 1000); - // this is hardly ever hit, but it loses all contents too often otherwise. - if ( (maybe > 372) || (maybe < 368) ) return true; - } - - // we hit the big time; we will reset now. - _hits[RESET - FIRST_TEST]++; - _the_table.reset(); - for (int i = _keys_in_use.elements() - 1; i >= 0; i--) { - int dead_key = _keys_in_use[i]; - ASSERT_FALSE(_the_table.acquire(dead_key), "after reset, we should not find item"); - _keys_in_use.remove(dead_key); - } - return true; -} - -//hmmm: implement these tests! - -bool test_hash_table::test_acquire_add_zap() -{ - _hits[ACQUIRE_ADD_ZAP - FIRST_TEST]++; -return false; -} - -bool test_hash_table::test_find_add_find() -{ - _hits[FIND_ADD_FIND - FIRST_TEST]++; -return false; -} - -bool test_hash_table::test_check_sanity() -{ - _hits[CHECK_SANITY - FIRST_TEST]++; -return false; -} - -////////////// - -HOOPLE_MAIN(test_hash_table, ) - diff --git a/core/library/tests_structures/test_int_hash.cpp b/core/library/tests_structures/test_int_hash.cpp deleted file mode 100644 index 1c888daa..00000000 --- a/core/library/tests_structures/test_int_hash.cpp +++ /dev/null @@ -1,553 +0,0 @@ -/* -* Name : test_int_hash -* Author : Chris Koeritz -* Purpose: -* Tests out hash_table specialization for integers. -** -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_INT_HASH - // uncomment for noisier run. - -#define EXTREME_CHECKING - // causes extra checks in the library code. - -const double TEST_DURATION = 0.5 * SECOND_ms; - -const int MAX_DEFAULT_BITS = 2; - // we start low since we will rehash occasionally. - -////////////// - -enum test_actions { - FIRST_TEST = 38, // place-holder. - ADD = FIRST_TEST, - // adds an item that is probably new. - ADD_ADD, - // adds an item that is probably new, followed by another item under the - // same key id. this ensures the overwriting gets tested. - ZAP, - // finds an item we know is in the list and whacks it. - ADD_ZAP, - // adds a new item and immediately finds and zaps it. - ZAP_ADD, - // zaps an item that we know about and then adds a new item with the same - // identifier. - FIND, - // locates an item in the list which we know should exist. - ACQUIRE, - // grabs an item out of the list (and tosses it). - FIND_ZAP_ADD, - // finds an item we know should exist, zaps it out of the list, then adds - // a new item with the same id. - ACQUIRE_ADD_ZAP, - // removes an item from the list that we know should be there, adds it back - // in, and then whacks it. - FIND_ADD_FIND, - // find an item with a particular id (one that we know should be in the - // list) and then adds a different item using the same id. the new item - // is then sought. - RESET, - // tosses all data out of the hash table. not done very often. - CHECK_SANITY, - // look for any problems or irregularities; print the contents of the list - // if any are found. - REHASH, - // resizes the hash table. - COPY, - // copies a hash table to another hash table. - LAST_TEST = COPY // place-holder; must equal test just prior. -}; - -////////////// - -// a simple object that is used as the contents of the hash_table. - -class data_shuttle -{ -public: - int food_bar; - astring snacky_string; - bool hungry; - byte_array chunk; - chaos chao; - - data_shuttle() - : snacky_string(string_manipulation::make_random_name()), - chunk(chao.inclusive(100, 10000)) {} -}; - -////////////// - -class test_int_hash : public virtual unit_base, virtual public application_shell -{ -public: - test_int_hash(); - - int execute(); - //!< the main startup for the test. - - bool perform_a_test(test_actions test_type); - //!< carries out the specifics of the "test_type". - - bool pick_a_test(); - //!< randomly picks one of the test types and performs it. - - DEFINE_CLASS_NAME("test_int_hash"); - - static bool equivalence_applier(const int &key, data_shuttle &item, void *dlink); - - static const char *test_name(test_actions test_type); - - int raw_random_id(); //!< returns an unvetted random number. - int unused_random_id(); //!< returns an unused (so far) random number. - - // these functions each perform one type of test, which their names indicate. - bool test_add(); - bool test_add_add(); - bool test_zap(); - bool test_add_zap(); - bool test_zap_add(); - bool test_find(); - bool test_acquire(); - bool test_find_zap_add(); - bool test_acquire_add_zap(); - bool test_find_add_find(); - bool test_reset(bool always_do_it = false); - bool test_check_sanity(); - bool test_copy(); - bool test_rehash(); - -private: - int_set _keys_in_use; // keys that we think are stored in the table. - int_hash _the_table; // our table under test. - int _hits[LAST_TEST - FIRST_TEST + 1]; // tracks our testing activities. - int _tested; // simple counter of number of test calls. -}; - -////////////// - -typedef int_hash our_hash; // cleans up somewhat. - -////////////// - -test_int_hash::test_int_hash() -: application_shell(), - _the_table(MAX_DEFAULT_BITS), - _tested(0) -{ - for (int i = FIRST_TEST; i <= LAST_TEST; i++) - _hits[i - FIRST_TEST] = 0; -} - -int test_int_hash::execute() -{ - time_stamp exit_time((int)TEST_DURATION); -//log(astring("before starting tests")); - while (time_stamp() < exit_time) { - pick_a_test(); - } - test_reset(true); // make sure we do this at least once. -#ifdef DEBUG_INT_HASH - log(a_sprintf("did %d tests.\n", _tested)); - log(astring("Test Activity:")); - for (int i = 0; i < LAST_TEST - FIRST_TEST + 1; i++) - log(astring(astring::SPRINTF, "%d (%s): %d hits", i + FIRST_TEST, - test_name(test_actions(i + FIRST_TEST)), _hits[i])); - log(a_sprintf("note that test %d will seldom be executed.", RESET)); -#endif - return final_report(); -} - -const char *test_int_hash::test_name(test_actions test_type) -{ - switch (test_type) { - case ADD: return "ADD"; - case ADD_ADD: return "ADD_ADD"; - case ZAP: return "ZAP"; - case ADD_ZAP: return "ADD_ZAP"; - case ZAP_ADD: return "ZAP_ADD"; - case FIND: return "FIND"; - case ACQUIRE: return "ACQUIRE"; - case FIND_ZAP_ADD: return "FIND_ZAP_ADD"; - case ACQUIRE_ADD_ZAP: return "ACQUIRE_ADD_ZAP"; - case FIND_ADD_FIND: return "FIND_ADD_FIND"; - case RESET: return "RESET"; - case COPY: return "COPY"; - case REHASH: return "REHASH"; - case CHECK_SANITY: return "CHECK_SANITY"; - default: return "UnknownTest"; - } -} - -bool test_int_hash::perform_a_test(test_actions test_type) -{ - FUNCDEF("perform_a_test"); - -// log(astring(test_name(test_type)) + " "); - - switch (test_type) { - case ADD: return test_add(); - case ADD_ADD: return test_add_add(); - case ZAP: return test_zap(); - case ADD_ZAP: return test_add_zap(); - case ZAP_ADD: return test_zap_add(); - case FIND: return test_find(); - case ACQUIRE: return test_acquire(); - case FIND_ZAP_ADD: return test_find_zap_add(); - case ACQUIRE_ADD_ZAP: return test_acquire_add_zap(); - case FIND_ADD_FIND: return test_find_add_find(); - case RESET: return test_reset(); - case COPY: return test_copy(); - case REHASH: return test_rehash(); - case CHECK_SANITY: return test_check_sanity(); - default: - ASSERT_TRUE(false, "there should be no missing case seen!"); - return false; // never gets here. - } -} - -bool test_int_hash::pick_a_test() -{ - _tested++; - return perform_a_test(test_actions(randomizer().inclusive(FIRST_TEST, LAST_TEST))); -} - -int test_int_hash::raw_random_id() -{ - return randomizer().inclusive(-MAXINT32 / 4, MAXINT32 / 4); -} - -int test_int_hash::unused_random_id() -{ - while (true) { - int checking = raw_random_id(); - if (!_keys_in_use.member(checking)) return checking; // got one. - } // keep going until we find unused id. -} - -bool test_int_hash::test_add() -{ - FUNCDEF("test_add"); - _hits[ADD - FIRST_TEST]++; - int random_id = raw_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - outcome wanting = common::IS_NEW; - if (_keys_in_use.member(random_id)) wanting = common::EXISTING; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), wanting.value(), - "adding key should work with right expectation"); - if (_keys_in_use.member(random_id)) - return true; // already was there so we replaced. - _keys_in_use.add(random_id); - return true; -} - -////////////// - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) - -int_hash *_hang_on = NIL; - // must be set before calling the apply method. - -bool test_int_hash::equivalence_applier(const int &key, data_shuttle &item, void *dlink) -{ - FUNCDEF("equivalence_applier"); - ASSERT_TRUE(dlink, "should have been given name"); - if (!dlink) return false; // fail. - astring test_name = (char *)dlink; - -//application_shell::single_instance()->log(astring("after name check")); - - data_shuttle *found = _hang_on->find(key); - ASSERT_TRUE(found, test_name + ": should find equivalent entry in second list"); - if (!found) return false; // bail or we'll crash. - -//application_shell::single_instance()->log(astring("after finding")); - - ASSERT_EQUAL(item.food_bar, found->food_bar, test_name + ": food_bar should not differ"); - ASSERT_EQUAL(item.snacky_string, found->snacky_string, test_name + ": snacky_string should not differ"); - ASSERT_EQUAL(item.hungry, found->hungry, test_name + ": hungry should not differ"); - return true; -} - -#undef UNIT_BASE_THIS_OBJECT -#define UNIT_BASE_THIS_OBJECT (*this) - -////////////// - -bool test_int_hash::test_rehash() -{ - FUNCDEF("test_rehash"); - // we don't want to rehash too often; it is expensive. -// int maybe = randomizer().inclusive(1, 50); -// if (maybe < 32) return true; // not this time. - - _hang_on = &_the_table; // must do this first. - - _hits[REHASH - FIRST_TEST]++; - - int_hash table_copy(_the_table.estimated_elements()); - - _the_table.apply(equivalence_applier, (void *)func); - astring second_test_name(func); - second_test_name += " try 2"; - table_copy.apply(equivalence_applier, (void *)second_test_name.s()); - - if (!_the_table.elements()) return true; // nothing to do right now for comparisons. - -//log("copying table..."); - copy_hash_table(table_copy, _the_table); - // make a copy of the table. - - ASSERT_INEQUAL(0, table_copy.elements(), "copy shouldn't have unexpected absence of contents"); - -//log("rehashing table..."); - _the_table.rehash(randomizer().inclusive(1, 20)); - -//hmmm: need test of dehash function that reduces elements estimated. - -//log("comparing table..."); - astring third_test_name(func); - third_test_name += " try 3"; - table_copy.apply(equivalence_applier, (void *)third_test_name.s()); -//log("done copy and compare."); - - return true; -} - -bool test_int_hash::test_copy() -{ - FUNCDEF("test_copy"); - // we don't want to copy too often. it's a heavy operation. -// int maybe = randomizer().inclusive(1, 50); -// if (maybe > 16) return true; // not this time. - - _hang_on = &_the_table; // must do this first. - - _hits[COPY - FIRST_TEST]++; - - int_hash table_copy(MAX_DEFAULT_BITS); - -//log("copying table..."); - copy_hash_table(table_copy, _the_table); - // make a copy of the table. - -//log("comparing table..."); - table_copy.apply(equivalence_applier, (void *)func); -//log("done copy and compare."); - - return true; -} - -////////////// - -bool test_int_hash::test_add_add() -{ - FUNCDEF("test_add_add"); - _hits[ADD_ADD - FIRST_TEST]++; - int random_id = unused_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, "key should be new"); - _keys_in_use.add(random_id); - - // second add on same id. - data_shuttle *next_add = new data_shuttle; - next_add->snacky_string = string_manipulation::make_random_name(); - next_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, next_add).value(), our_hash::EXISTING, - "second add should not say first failed"); - - return true; -} - -bool test_int_hash::test_zap() -{ - FUNCDEF("test_zap"); - int maybe = randomizer().inclusive(1, 1000); - if (maybe > 500) return true; - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ZAP - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int dead_key = _keys_in_use[rand_indy]; - _keys_in_use.remove(dead_key); // remove the record of that key. - ASSERT_TRUE(_the_table.zap(dead_key), "zap should work on key"); - return true; -} - -bool test_int_hash::test_add_zap() -{ - FUNCDEF("test_add_zap"); - // add. - _hits[ADD_ZAP - FIRST_TEST]++; - int random_id = unused_random_id(); - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = random_id; - ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, "key is new before zap"); - // zap. - ASSERT_TRUE(_the_table.zap(random_id), "add then zap should remove the key"); - return true; -} - -bool test_int_hash::test_zap_add() -{ - FUNCDEF("test_zap_add"); - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ZAP_ADD - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - // in the end, the key list state won't be changed unless the test fails. - int dead_key = _keys_in_use[rand_indy]; - ASSERT_TRUE(_the_table.zap(dead_key), "key should be there for zapping"); - - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = dead_key; - outcome ret = _the_table.add(dead_key, to_add); - ASSERT_EQUAL(ret.value(), our_hash::IS_NEW, "key should not already be present somehow"); - return true; -} - -int functional_return(int to_pass) { - return to_pass; -} - -bool test_int_hash::test_find() -{ - FUNCDEF("test_find"); - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[FIND - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int find_key = _keys_in_use[rand_indy]; - data_shuttle *found = NIL; - ASSERT_TRUE(_the_table.find(functional_return(find_key), found), - "key should be there when we look"); - ASSERT_TRUE(_the_table.find(functional_return(find_key)), "find2: key be there when checked"); - ASSERT_TRUE(found, "when key is found contents should not be NIL"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should have some length"); - return true; -} - -bool test_int_hash::test_acquire() -{ - FUNCDEF("test_acquire"); - int maybe = randomizer().inclusive(1, 1000); - if (maybe > 750) return true; - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[ACQUIRE - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - int find_key = _keys_in_use[rand_indy]; - _keys_in_use.remove(find_key); // remove the record of that key. - data_shuttle *found = _the_table.acquire(find_key); - ASSERT_TRUE(found, "key should be there like clockwork"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should have some length"); - WHACK(found); - found = _the_table.acquire(find_key); - ASSERT_FALSE(found, "key should be missing after zap"); - return true; -} - -bool test_int_hash::test_find_zap_add() -{ - FUNCDEF("test_find_zap_add"); - // find. - if (!_keys_in_use.elements()) return false; // can't do it yet. - _hits[FIND_ZAP_ADD - FIRST_TEST]++; - int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); - // this is another key list invariant function, if it works. - int find_key = _keys_in_use[rand_indy]; - data_shuttle *found = NIL; - ASSERT_TRUE(_the_table.find(find_key, found), "key should be there when sought"); - ASSERT_TRUE(_the_table.find(find_key), "find2: key should be there for us to find"); - ASSERT_TRUE(found, "found key should have non-NIL contents"); - ASSERT_EQUAL(found->food_bar, find_key, "stored key should have no differences from real key"); - ASSERT_TRUE(found->snacky_string.length(), "stored string should have non-zero length"); - // zap. - ASSERT_TRUE(_the_table.zap(find_key), "should be able to zap the item we had found"); - // add. - data_shuttle *to_add = new data_shuttle; - to_add->snacky_string = string_manipulation::make_random_name(); - to_add->food_bar = find_key; - ASSERT_EQUAL(_the_table.add(find_key, to_add).value(), our_hash::IS_NEW, - "the item we zapped should not still be there"); - return true; -} - -bool test_int_hash::test_reset(bool always_do_it) -{ - FUNCDEF("test_reset"); - if (!always_do_it) { - int maybe = randomizer().inclusive(1, 1000); - // this is hardly ever hit, but it loses all contents too often otherwise. - if ( (maybe > 372) || (maybe < 368) ) return true; - } - - // we hit the big time; we will reset now. - _hits[RESET - FIRST_TEST]++; - _the_table.reset(); - for (int i = _keys_in_use.elements() - 1; i >= 0; i--) { - int dead_key = _keys_in_use[i]; - ASSERT_FALSE(_the_table.acquire(dead_key), "after reset, we should not find an item"); - _keys_in_use.remove(dead_key); - } - return true; -} - -//hmmm: implement these tests! - -bool test_int_hash::test_acquire_add_zap() -{ - _hits[ACQUIRE_ADD_ZAP - FIRST_TEST]++; -return false; -} - -bool test_int_hash::test_find_add_find() -{ - _hits[FIND_ADD_FIND - FIRST_TEST]++; -return false; -} - -bool test_int_hash::test_check_sanity() -{ - _hits[CHECK_SANITY - FIRST_TEST]++; -return false; -} - -////////////// - -HOOPLE_MAIN(test_int_hash, ) - diff --git a/core/library/tests_structures/test_matrix.cpp b/core/library/tests_structures/test_matrix.cpp deleted file mode 100644 index bb87e1e0..00000000 --- a/core/library/tests_structures/test_matrix.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* -* Name : test_matrix -* Author : Chris Koeritz -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_MATRIX - // uncomment for noisier version. - -const int DIM_ROWS = 10; -const int DIM_COLS = 10; - -// fills the matrix "to_stuff" with the pure version of the exemplar. -#define STUFF_MATRIX(to_stuff, max_row, max_col) \ - to_stuff.reset(max_row, max_col); \ - for (int r = 0; r < max_row; r++) \ - for (int c = 0; c < max_col; c++) \ - to_stuff.put(r, c, test_pure.get(r, c)) - -////////////// - -// forward. -class my_int_matrix; - -////////////// - -// this class exhibits an old bug where the matrix was zeroing out its -// contents for a same size resize. the zeroing allowed hell to spew forth. -class diggulite -{ -public: - diggulite() {} - virtual ~diggulite() {} -}; - -////////////// - -class test_matrix : virtual public unit_base, virtual public application_shell -{ -public: - test_matrix(); - - DEFINE_CLASS_NAME("test_matrix"); - - void log(const astring &to_print) { application_shell::log(to_print); } - // override to avoid redoing all the logs here. - - int execute(); - // performs main body of test. - - static astring dump_matrix(const my_int_matrix &to_print); - // creates a nice form of the matrix "to_print". - - void print_matrix(const my_int_matrix &to_print); - // dumps "to_print" to the diagnostic output. - - void test_out_submatrix(const my_int_matrix &source); - //!< runs some tests on the submatrix() function. - - void test_out_redimension(); - //!< checks out the redimension() method for resizing the array. - - void test_out_resizing_virtual_objects(); - //!< checks that a matrix of non-simple objects doesn't have bad problems. - - void test_out_zapping(const my_int_matrix &test_pure); - //!< tries out zap operations. - - void test_out_inserting(const my_int_matrix &test_pure); - //!< checks the insert row and column methods. -}; - -////////////// - -class my_int_matrix : public int_matrix, virtual public hoople_standard -{ -public: - my_int_matrix(int r = 0, int c = 0) : int_matrix(r, c) {} - my_int_matrix(const int_matrix &init) : int_matrix(init) {} - - DEFINE_CLASS_NAME("my_int_matrix"); - - virtual bool equal_to(const equalizable &s2) const { - const my_int_matrix *sec = dynamic_cast(&s2); - if (!sec) return false; - if (rows() != sec->rows()) return false; - if (columns() != sec->columns()) return false; - for (int r = 0; r < this->rows(); r++) - for (int c = 0; c < this->columns(); c++) - if ((*this)[r][c] != (*sec)[r][c]) return false; - return true; - } - - virtual void text_form(base_string &state_fill) const { - state_fill.assign(test_matrix::dump_matrix(*this)); - } -}; - -////////////// - -test_matrix::test_matrix() : application_shell() {} - -astring test_matrix::dump_matrix(const my_int_matrix &to_print) -{ - astring text; - for (int t = 0; t < to_print.rows(); t++) { - text += astring(astring::SPRINTF, "[%d] ", t); - for (int c = 0; c < to_print.columns(); c++) - text += astring(astring::SPRINTF, "%03d ", int(to_print[t][c])); - text += parser_bits::platform_eol_to_chars(); - } - return text; -} - -void test_matrix::print_matrix(const my_int_matrix &to_print) -{ log(astring("\n") + dump_matrix(to_print)); } - -void test_matrix::test_out_submatrix(const my_int_matrix &source) -{ - FUNCDEF("test_out_submatrix") - my_int_matrix test2(source); - - for (int s = 0; s < DIM_ROWS; s++) - for (int c = 0; c < DIM_COLS; c++) - ASSERT_EQUAL(source[s][c], test2[s][c], "computed matrices should be same after copy"); - -#ifdef DEBUG_MATRIX - log("before submatrix:"); - print_matrix(test2); -#endif - my_int_matrix chunk(test2.submatrix(2, 3, 3, 2)); - my_int_matrix chunk_comparator(3, 2); - for (int r = 0; r < 3; r++) - for (int c = 0; c < 2; c++) - chunk_comparator[r][c] = test2[r+2][c+3]; - ASSERT_EQUAL(chunk, chunk_comparator, "submatrix should grab proper contents"); -#ifdef DEBUG_MATRIX - log("after submatrix, chunk of the matrix has:"); - print_matrix(chunk); -#endif -} - -void test_matrix::test_out_redimension() -{ - FUNCDEF("test_out_redimension") - my_int_matrix computed(7, 14); - for (int x1 = 0; x1 < 7; x1++) { - for (int y1 = 0; y1 < 14; y1++) { - if ( (x1 * y1) % 2) computed[x1][y1] = 1 + x1 * 100 + y1; - else computed.put(x1, y1, 1 + x1 * 100 + y1); - } - } - - for (int x2 = 6; x2 >= 0; x2--) { - for (int y2 = 13; y2 >= 0; y2--) { - ASSERT_EQUAL(computed[x2][y2], 1 + x2 * 100 + y2, - "computed matrix should have proper computed values"); - } - } - - computed.redimension(3, 5); - ASSERT_FALSE( (computed.rows() != 3) || (computed.columns() != 5), - "redimension should not get size wrong"); - for (int x3 = 2; x3 >= 0; x3--) { - for (int y3 = 4; y3 >= 0; y3--) { - ASSERT_EQUAL(computed[x3][y3], 1 + x3 * 100 + y3, - "computed matrix should still have right values"); - } - } - - computed.redimension(0, 0); - ASSERT_FALSE(computed.rows() || computed.columns(), - "redimension to zero should see matrix as empty"); - - computed.reset(12, 20); - ASSERT_FALSE( (computed.rows() != 12) || (computed.columns() != 20), - "resize should compute proper size"); -} - -void test_matrix::test_out_resizing_virtual_objects() -{ - FUNCDEF("test_out_resizing_virtual_objects") - // this test block ensures that the matrix doesn't blow up from certain - // resizing operations performed on a templated type that has a virtual - // destructor. - matrix grids; - grids.reset(); - grids.redimension ( 0, 1 ); - grids.redimension ( 1, 1 ); - grids.reset(1, 1); - ASSERT_TRUE(true, "no explosions should occur due to virtual contents"); -} - -void test_matrix::test_out_zapping(const my_int_matrix &test_pure) -{ - FUNCDEF("test_out_zapping") - // this block tests the zapping ops. - my_int_matrix test_zap; - STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); - -#ifdef DEBUG_MATRIX - log("matrix before zappage:"); - print_matrix(test_zap); -#endif - - my_int_matrix compare_1 = test_zap; - ASSERT_EQUAL(compare_1, test_zap, "assignment works right"); - test_zap.zap_row(5); - // make same changes but with different ops so we can compare. - for (int r = 6; r < DIM_ROWS; r++) - for (int c = 0; c < DIM_COLS; c++) - compare_1[r - 1][c] = compare_1[r][c]; - compare_1.zap_row(DIM_ROWS - 1); // lose the last row now. - ASSERT_EQUAL(compare_1, test_zap, "zapping should work regardless of path"); - -#ifdef DEBUG_MATRIX - log("matrix after zappage of row 5:"); - print_matrix(test_zap); -#endif - - // reset the array again. - STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); - my_int_matrix compare_2 = test_zap; - test_zap.zap_column(3); - // now make those same changes in our compare array. - for (int r = 0; r < DIM_ROWS; r++) - for (int c = 4; c < DIM_COLS; c++) - compare_2[r][c - 1] = compare_2[r][c]; - compare_2.zap_column(DIM_COLS - 1); // lose the last row now. - ASSERT_EQUAL(compare_2, test_zap, "second zapping should work regardless of path"); - -#ifdef DEBUG_MATRIX - log("matrix after zappage of column 3:"); - print_matrix(test_zap); -#endif - - // reset test_zap again. - STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); - my_int_matrix compare_3(test_zap.submatrix(1, 1, DIM_ROWS - 2, DIM_COLS - 2)); - test_zap.zap_column(0); - test_zap.zap_row(0); - test_zap.zap_row(test_zap.rows() - 1); - test_zap.zap_column(test_zap.columns() - 1); - ASSERT_EQUAL(test_zap, compare_3, "zapping and submatrix should compute same result"); - -#ifdef DEBUG_MATRIX - log("matrix after zap of row 0, col 0, last row, last col"); - print_matrix(test_zap); -#endif -} - -void test_matrix::test_out_inserting(const my_int_matrix &test_pure) -{ - FUNCDEF("test_out_inserting") - // this block tests the inserting ops. - my_int_matrix test_insert; - STUFF_MATRIX(test_insert, 4, 4); - -#ifdef DEBUG_MATRIX - log("matrix before inserting:"); - print_matrix(test_insert); -#endif - - my_int_matrix compare_1(test_insert); - test_insert.insert_row(2); - compare_1.insert_row(4); - for (int r = 3; r >= 2; r--) - for (int c = 0; c < 4; c++) - compare_1[r + 1][c] = compare_1[r][c]; - for (int c = 0; c < 4; c++) - compare_1[2][c] = 0; - ASSERT_EQUAL(test_insert, compare_1, "inserting row should create expected array"); - -#ifdef DEBUG_MATRIX - log("matrix after insert of row 2:"); - print_matrix(test_insert); -#endif - - // reset test_insert again. - STUFF_MATRIX(test_insert, 5, 6); - -#ifdef DEBUG_MATRIX - log("reset matrix before inserting:"); - print_matrix(test_insert); -#endif - - my_int_matrix compare_2(test_insert); - test_insert.insert_column(3); - compare_2.insert_column(6); - for (int r = 0; r < 5; r++) - for (int c = 5; c >= 3; c--) - compare_2[r][c + 1] = compare_2[r][c]; - for (int r = 0; r < 5; r++) - compare_2[r][3] = 0; - ASSERT_EQUAL(test_insert, compare_2, "inserting column should create expected array"); - -#ifdef DEBUG_MATRIX - log("matrix after insert of column 3:"); - print_matrix(test_insert); -#endif - - // reset test_insert again. - STUFF_MATRIX(test_insert, 3, 3); - my_int_matrix compare_3(5, 5); - for (int r = 0; r < 3; r++) - for (int c = 0; c < 3; c++) - compare_3[r + 1][c + 1] = test_insert[r][c]; - for (int r = 0; r < 5; r++) { compare_3[r][0] = 0; compare_3[r][4] = 0; } - for (int c = 0; c < 5; c++) { compare_3[0][c] = 0; compare_3[4][c] = 0; } - -#ifdef DEBUG_MATRIX - log("matrix before inserting:"); - print_matrix(test_insert); -#endif - - test_insert.insert_column(0); - -#ifdef DEBUG_MATRIX - log("insert col at 0"); - print_matrix(test_insert); -#endif - - test_insert.insert_row(test_insert.rows()); - -#ifdef DEBUG_MATRIX - log("insert row at rows()"); - print_matrix(test_insert); -#endif - - test_insert.insert_column(test_insert.columns()); - -#ifdef DEBUG_MATRIX - log("insert col at cols()"); - print_matrix(test_insert); - log("insert row at 0..."); -#endif - - test_insert.insert_row(0); - - ASSERT_EQUAL(test_insert, compare_3, - "inserting some rows and columns should create expected array"); - -#ifdef DEBUG_MATRIX - log(astring("matrix after insert of col 0, last row, last col, row 0")); - print_matrix(test_insert); -#endif -} - -int test_matrix::execute() -{ - FUNCDEF("execute"); - - my_int_matrix test_pure(DIM_ROWS, DIM_COLS); // kept without modification. - for (int r = 0; r < DIM_ROWS; r++) - for (int c = 0; c < DIM_COLS; c++) - test_pure[r][c] = r * DIM_COLS + c; - - my_int_matrix test1 = test_pure; // first copy to work with. - - test1.reset(); - ASSERT_FALSE(test1.rows() || test1.columns(), "after reset matrix should be empty"); - - test_out_submatrix(test_pure); - - test_out_redimension(); - - test_out_resizing_virtual_objects(); - - test_out_zapping(test_pure); - - test_out_inserting(test_pure); - - return final_report(); -} - -HOOPLE_MAIN(test_matrix, ) - diff --git a/core/library/tests_structures/test_memory_limiter.cpp b/core/library/tests_structures/test_memory_limiter.cpp deleted file mode 100644 index e0fec5eb..00000000 --- a/core/library/tests_structures/test_memory_limiter.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_memory_limiter * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Tests that the memory_limiter is keeping track of the memory users * -* accurately. * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -//#define DEBUG_MEMORY_LIMITER - // uncomment for debugging version. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DEBUG_MEMORY_LIMITER - #include -#endif - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -const int MAXIMUM_MEM_OVERALL = 1 * MEGABYTE; -const int MAXIMUM_MEM_PER_OWNER = 100 * KILOBYTE; - -const int RUN_TIME = .8 * SECOND_ms; - -////////////// - -class test_memory_limiter : virtual public unit_base, virtual public application_shell -{ -public: - test_memory_limiter() {} - DEFINE_CLASS_NAME("test_memory_limiter"); - virtual int execute(); -}; - -////////////// - -struct mem_record { - int parent; - int allocated; - - mem_record(int parent_in = 0, int allocated_in = 0) - : parent(parent_in), allocated(allocated_in) {} -}; - -struct memorial : array {}; - -////////////// - -int test_memory_limiter::execute() -{ - FUNCDEF("execute"); - time_stamp when_to_leave(RUN_TIME); - time_stamp start; - memorial wtc; - memory_limiter to_test(MAXIMUM_MEM_OVERALL, MAXIMUM_MEM_PER_OWNER); - int allocations = 0; - int deletions = 0; - basis::un_int total_allocated = 0; - basis::un_int total_deleted = 0; - while (time_stamp() < when_to_leave) { - int to_do = randomizer().inclusive(1, 100); - if (to_do < 50) { - // add a new record. - int alloc = randomizer().inclusive(1, 1 * MEGABYTE); -//isolate min max alloc - int parent = randomizer().inclusive(1, 120); -//isolate min max parents - - if (!to_test.okay_allocation(parent, alloc)) - continue; // no space right now. - wtc += mem_record(parent, alloc); - allocations++; - total_allocated += alloc; - } else if (to_do < 88) { - // remove an existing record. - if (!wtc.length()) continue; // nothing to remove. - int indy = randomizer().inclusive(0, wtc.length() - 1); - mem_record to_gone = wtc[indy]; - wtc.zap(indy, indy); - ASSERT_TRUE(to_test.record_deletion(to_gone.parent, to_gone.allocated), - "first case failed to record deletion!"); - deletions++; - total_deleted += to_gone.allocated; - } else { -//do something funky, like allocate part of one into another... - } - } - - // now clear everything left in our list. - for (int i = 0; i < wtc.length(); i++) { - mem_record to_gone = wtc[i]; - ASSERT_TRUE(to_test.record_deletion(to_gone.parent, to_gone.allocated), - "second case failed to record deletion!"); - deletions++; - total_deleted += to_gone.allocated; - } - - // now check that the memory limiter has returned to camber. - - ASSERT_FALSE(to_test.overall_usage(), "final checks: there is still memory in use!"); - - ASSERT_EQUAL(to_test.overall_space_left(), MAXIMUM_MEM_OVERALL, - "final checks: the free space is not correct!"); - - int_set remaining = to_test.individuals_listed(); - ASSERT_FALSE(remaining.elements(), "final checks: there were still uncleared individuals!"); - - time_stamp end; - - LOG("stats for this run:"); - LOG(astring(astring::SPRINTF, "\trun time %f ms", - end.value() - start.value())); - LOG(astring(astring::SPRINTF, "\tallocations %d, total memory allocated %d", - allocations, total_allocated)); - LOG(astring(astring::SPRINTF, "\tdeletions %d, total memory deleted %d", - deletions, total_deleted)); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_memory_limiter, ); - diff --git a/core/library/tests_structures/test_packing.cpp b/core/library/tests_structures/test_packing.cpp deleted file mode 100644 index 13e369d6..00000000 --- a/core/library/tests_structures/test_packing.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* -* Name : test_object_packing -* Author : Chris Koeritz -** -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace unit_test; - -#define GENERATE_TEST_NAME(group, message) \ - (astring(group) + " test group: " + message) - -#define TRY_ON(type, value, group) { \ - byte_array temp_array; \ - attach(temp_array, type(value)); \ - type output; \ -/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ - ASSERT_TRUE(detach(temp_array, output), \ - GENERATE_TEST_NAME(group, "should unpack " #type " okay")); \ - ASSERT_TRUE(output == type(value), \ - GENERATE_TEST_NAME(group, #type " value should match")); \ - ASSERT_FALSE(temp_array.length(), \ - GENERATE_TEST_NAME(group, #type " detached should be empty")); \ -} - -#define TRY_ON_OBSCURE(type, value, group) { \ - byte_array temp_array; \ - obscure_attach(temp_array, type(value)); \ - type output; \ -/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ - ASSERT_TRUE(obscure_detach(temp_array, output), \ - GENERATE_TEST_NAME(group, "should obscure unpack " #type " okay")); \ - ASSERT_TRUE(output == type(value), \ - GENERATE_TEST_NAME(group, #type " value should obscure match")); \ - ASSERT_FALSE(temp_array.length(), \ - GENERATE_TEST_NAME(group, #type " obscure detached should be empty")); \ -} - -#define TRY_ON_F(type, value, group) { \ - byte_array temp_array; \ - attach(temp_array, type(value)); \ - type output; \ -/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ - ASSERT_TRUE(detach(temp_array, output), \ - GENERATE_TEST_NAME(group, "should unpack " #type " fine")); \ -/* double_plus a(output); \ - double_plus b(value); */ \ - /*double diff = maximum(output, value) - minimum(output, value);*/ \ - int exponent_1, exponent_2; \ - double mantissa_1 = frexp(output, &exponent_1); \ - double mantissa_2 = frexp(output, &exponent_2); \ - ASSERT_FALSE( (mantissa_1 != mantissa_2) || (exponent_1 != exponent_2), \ - GENERATE_TEST_NAME(group, #type " value should match just so")); \ - ASSERT_FALSE(temp_array.length(), \ - GENERATE_TEST_NAME(group, #type " detached should have no data left")); \ -} - -class test_object_packing : virtual public unit_base, virtual public application_shell -{ -public: - test_object_packing() : application_shell() {} - ~test_object_packing() {} - - DEFINE_CLASS_NAME("test_object_packing"); - - int execute(); -}; - -////////////// - -int test_object_packing::execute() -{ - FUNCDEF("execute"); - { - #define TEST "first" - TRY_ON(int, 2383, TEST); - TRY_ON(int, -18281, TEST); -// TRY_ON(long, 337628, TEST); -// TRY_ON(long, -987887, TEST); - TRY_ON(short, 12983, TEST); - TRY_ON(short, -32700, TEST); - TRY_ON(int, 2988384, TEST); - TRY_ON(int, 92982984, TEST); -// TRY_ON(un_long, 388745, TEST); -// TRY_ON(un_long, 993787, TEST); - TRY_ON(basis::un_short, 12983, TEST); - TRY_ON(basis::un_short, 48377, TEST); - TRY_ON_OBSCURE(un_int, -23948377, TEST); - TRY_ON_OBSCURE(un_int, 28938, TEST); - #undef TEST - } - { - #define TEST "second" - TRY_ON(int, 0, TEST); - TRY_ON(int, MAXINT32, TEST); - TRY_ON(int, MININT32, TEST); - TRY_ON(abyte, 0, TEST); - TRY_ON(abyte, MAXBYTE, TEST); - TRY_ON(abyte, MINBYTE, TEST); - TRY_ON(char, 0, TEST); - TRY_ON(char, MAXCHAR, TEST); - TRY_ON(char, MINCHAR, TEST); -// TRY_ON(long, 0, TEST); -// TRY_ON(long, MAXLONG, TEST); -// TRY_ON(long, MINLONG, TEST); - TRY_ON(short, 0, TEST); - TRY_ON(short, MAXINT16, TEST); - TRY_ON(short, MININT16, TEST); - TRY_ON(basis::un_int, 0, TEST); - un_int max_u_int = MAXINT32 | MININT32; - TRY_ON(basis::un_int, max_u_int, TEST); - TRY_ON(basis::un_int, max_u_int - 1, TEST); - TRY_ON(basis::un_int, max_u_int - 2, TEST); - TRY_ON(basis::un_int, max_u_int - 3, TEST); -// un_long max_u_long = MAXLONG | MINLONG; -// TRY_ON(un_long, 0, TEST); -// TRY_ON(un_long, max_u_long, TEST); -// TRY_ON(un_long, max_u_long - 1, TEST); -// TRY_ON(un_long, max_u_long - 2, TEST); -// TRY_ON(un_long, max_u_long - 3, TEST); - basis::un_short max_u_short = MAXINT16 | MININT16; - TRY_ON(basis::un_short, 0, TEST); - TRY_ON(basis::un_short, max_u_short, TEST); - TRY_ON(basis::un_short, max_u_short - 1, TEST); - TRY_ON(basis::un_short, max_u_short - 2, TEST); - TRY_ON(basis::un_short, max_u_short - 3, TEST); - #undef TEST - } - { - #define TEST "third" - // new bit for floating point packing. - TRY_ON_F(double, 0.0, TEST); - TRY_ON_F(double, 1.0, TEST); - TRY_ON_F(double, -1.0, TEST); - TRY_ON_F(double, 1.1, TEST); - TRY_ON_F(double, -1.1, TEST); - TRY_ON_F(double, 1983.293, TEST); - TRY_ON_F(double, -1983.293, TEST); - TRY_ON_F(double, 984.293e20, TEST); - TRY_ON_F(double, -984.293e31, TEST); - - const int MAX_FLOAT_ITERS = 100; - int iters = 0; - while (iters++ < MAX_FLOAT_ITERS) { - double dividend = randomizer().inclusive(1, MAXINT32 / 2); - double divisor = randomizer().inclusive(1, MAXINT32 / 2); - double multiplier = randomizer().inclusive(1, MAXINT32 / 2); - double rand_float = (dividend / divisor) * multiplier; - if (randomizer().inclusive(0, 1) == 1) - rand_float = -1.0 * rand_float; - TRY_ON_F(double, rand_float, "third--loop"); -//log(a_sprintf("%f", rand_float)); - } - #undef TEST - } - - { - #define TEST "fourth" - // new test for char * packing. - const char *tunnel_vision = "plants can make good friends."; - const char *fresnel_lense = "chimney sweeps carry some soot."; - const char *snoopy = "small white dog with black spots."; - byte_array stored; - int fregose = 38861; - double perky_doodle = 3799.283e10; - const char *emptyish = ""; - int jumboat = 998; - // now stuff the array with some things. - attach(stored, fregose); - attach(stored, tunnel_vision); - attach(stored, snoopy); - attach(stored, perky_doodle); - attach(stored, fresnel_lense); - attach(stored, emptyish); - attach(stored, jumboat); - // time to restore those contents. - astring tunnel_copy; - astring fresnel_copy; - astring snoopy_copy; - int freg_copy; - double perk_copy; - astring emp_copy; - int jum_copy; - ASSERT_TRUE(detach(stored, freg_copy), - GENERATE_TEST_NAME(TEST, "first int failed to unpack")); - ASSERT_TRUE(detach(stored, tunnel_copy), - GENERATE_TEST_NAME(TEST, "first string failed to unpack")); - ASSERT_TRUE(detach(stored, snoopy_copy), - GENERATE_TEST_NAME(TEST, "second string failed to unpack")); - ASSERT_TRUE(detach(stored, perk_copy), - GENERATE_TEST_NAME(TEST, "first double failed to unpack")); - ASSERT_TRUE(detach(stored, fresnel_copy), - GENERATE_TEST_NAME(TEST, "third string failed to unpack")); - ASSERT_TRUE(detach(stored, emp_copy), - GENERATE_TEST_NAME(TEST, "fourth string failed to unpack")); - ASSERT_TRUE(detach(stored, jum_copy), - GENERATE_TEST_NAME(TEST, "second int failed to unpack")); - // now test contents. - ASSERT_EQUAL(freg_copy, fregose, - GENERATE_TEST_NAME(TEST, "first int had wrong contents")); - ASSERT_EQUAL(tunnel_copy, astring(tunnel_vision), - GENERATE_TEST_NAME(TEST, "first string had wrong contents")); - ASSERT_EQUAL(snoopy_copy, astring(snoopy), - GENERATE_TEST_NAME(TEST, "second string had wrong contents")); - ASSERT_EQUAL(perk_copy, perky_doodle, - GENERATE_TEST_NAME(TEST, "first double had wrong contents")); - ASSERT_EQUAL(fresnel_copy, astring(fresnel_lense), - GENERATE_TEST_NAME(TEST, "third string had wrong contents")); - ASSERT_EQUAL(emp_copy, astring(emptyish), - GENERATE_TEST_NAME(TEST, "fourth string had wrong contents")); - ASSERT_EQUAL(jum_copy, jumboat, - GENERATE_TEST_NAME(TEST, "second int had wrong contents")); - ASSERT_FALSE(stored.length(), - GENERATE_TEST_NAME(TEST, "array still had contents after detaching")); - #undef TEST - } - -// critical_events::alert_message("packable:: works for those functions tested."); - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_object_packing, ); - diff --git a/core/library/tests_structures/test_set.cpp b/core/library/tests_structures/test_set.cpp deleted file mode 100644 index 06175059..00000000 --- a/core/library/tests_structures/test_set.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_set * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -class test_set : virtual public unit_base, virtual public application_shell -{ -public: - test_set() {} - DEFINE_CLASS_NAME("test_set"); - virtual int execute(); -}; - -int test_set::execute() -{ - FUNCDEF("execute"); - int_set fred; - ASSERT_TRUE(fred.empty(), "first empty check should work"); - ASSERT_TRUE(fred.add(23), "fred 1st add should go in"); - ASSERT_TRUE(fred.add(90123), "fred 2nd add should be okay"); - ASSERT_FALSE(fred.add(23), "fred 3rd add works fine"); - ASSERT_FALSE(fred.add(90123), "fred 4th add is good"); - ASSERT_FALSE(fred.empty(), "second empty check should work"); - ASSERT_TRUE(fred.non_empty(), "non_empty check should be right"); - - int_set gen; - ASSERT_TRUE(gen.add(13), "gen 1st add is okay"); - ASSERT_TRUE(gen.add(23), "gen 2nd add should be fine"); - ASSERT_TRUE(gen.add(8012), "gen 3rd add was good"); - - int_set intersect(gen.intersection(fred)); - ASSERT_EQUAL(intersect.elements(), 1, "intersection elements should be one"); - ASSERT_TRUE(intersect.member(23), "element should be present as 23"); - - int_set uni(gen.set_union(fred)); - ASSERT_EQUAL(uni.elements(), 4, "union elements should be correct"); - ASSERT_TRUE(uni.member(23), "first element we seek should be present"); - ASSERT_TRUE(uni.member(90123), "second element we seek should be present"); - ASSERT_TRUE(uni.member(13), "third element we seek should be present"); - ASSERT_TRUE(uni.member(8012), "fourth element we seek should be present"); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_set, ) - diff --git a/core/library/tests_structures/test_stack.cpp b/core/library/tests_structures/test_stack.cpp deleted file mode 100644 index 231931ec..00000000 --- a/core/library/tests_structures/test_stack.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/* -* Name : t_stack -* Author : Chris Koeritz -* Purpose: -* Tests out the stack object with both flat objects (byte_array) but also -* deep objects (pointer to byte_array). Both bounded and unbounded stacks -* are tested for each. -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DEBUG_STACK - #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) -#endif - -#include -#include - -using namespace application; -using namespace basis; -///using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//HOOPLE_STARTUP_CODE; - -//#define DEBUG_STACK - // uncomment for a noisier version. - -const int test_iterations = 40; - - -class test_stack : public virtual unit_base, public virtual application_shell -{ -public: - test_stack() {} - DEFINE_CLASS_NAME("test_stack"); - int execute(); - - void test_stack_with_objects(); - void test_stack_with_pointers(); - - void CHECK_STACK_RESULT(outcome retval, const astring &place); - byte_array generate_flat(const char *to_store); - byte_array *generate_deep(const char *to_store); - astring text_form(stack &s); - astring text_form(stack &s); -}; - -// CHECK_STACK_RESULT: a function that takes care of error testing for a -// stack command. if executing the command ends in full or empty, then -// that is reported. -void test_stack::CHECK_STACK_RESULT(outcome retval, const astring &place) -{ -#ifdef DEBUG_STACK - if (retval == common::IS_FULL) - LOG(astring(astring::SPRINTF, "returned IS_FULL at %s", place.s())) - else if (retval == common::IS_EMPTY) - LOG(astring(astring::SPRINTF, "returned IS_EMPTY at %s", place.s())); -#else - if (retval.value() || !place) {} -#endif -} - -byte_array test_stack::generate_flat(const char *to_store) -{ - byte_array to_return = byte_array(int(strlen(to_store) + 1), (abyte *)to_store); - return to_return; -} - -byte_array *test_stack::generate_deep(const char *to_store) -{ - byte_array *to_return = new byte_array(int(strlen(to_store) + 1), - (abyte *)to_store); - return to_return; -} - -astring test_stack::text_form(stack &s) -{ - astring to_return; - for (int i = 0; i < s.elements(); i++) { - to_return += astring(astring::SPRINTF, "#%d: %s\n", i, s[i]->observe()); - } - return to_return; -} - -astring test_stack::text_form(stack &s) -{ - astring to_return; - for (int i = 0; i < s.elements(); i++) { - to_return += astring(astring::SPRINTF, "#%d: %s\n", i, s[i].observe()); - } - return to_return; -} - -void test_stack::test_stack_with_objects() -{ - FUNCDEF("test_stack_with_objects"); - for (int qq = 0; qq < test_iterations; qq++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "index %d", qq)); -#endif - stack bounded_stack(3); - stack unlimited_stack(0); - chaos randomizer; - - { -#ifdef DEBUG_STACK - LOG("testing the bounded stack first:"); -#endif - CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the first line")), "first push"); - CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the second line")), "second push"); - CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the final and third")), "third push"); - byte_array gend = generate_flat("this shouldn't work"); - ASSERT_EQUAL(bounded_stack.push(gend).value(), common::IS_FULL, - "the bounded stack push should catch IS_FULL"); -#ifdef DEBUG_STACK - LOG("pushing worked successfully..."); - LOG("printing the stack in element order"); -#endif - for (int i = 0; i < bounded_stack.size(); i++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "depth %d has %s.", i, - bounded_stack[i].observe())); -#endif - } -#ifdef DEBUG_STACK - LOG("now popping the stack all the way back."); -#endif - int full_size = bounded_stack.size(); - for (int j = 0; j < full_size; j++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "pop %d, stack size is %d, has %s", j+1, - bounded_stack.size(), bounded_stack.top().observe())); -#endif - byte_array found; - bounded_stack.acquire_pop(found); - astring got((char *)found.observe()); - switch (j) { - case 0: - ASSERT_EQUAL(got, astring("the final and third"), - "acquire_pop should have right contents at 2"); - break; - case 1: - ASSERT_EQUAL(got, astring("the second line"), - "acquire_pop should have right contents at 1"); - break; - case 2: - ASSERT_EQUAL(got, astring("the first line"), - "acquire_pop should have right contents at 0"); - break; - } - } - ASSERT_EQUAL(bounded_stack.pop().value(), common::IS_EMPTY, - "bounded pop should have right outcome"); - } - - { -#ifdef DEBUG_STACK - LOG("testing the unbounded stack now:"); -#endif - for (int j = 0; j < 24; j++) { - astring line(astring::SPRINTF, "{posn %d here}", j); - CHECK_STACK_RESULT(unlimited_stack.push(generate_flat(line.s())), "unbound push"); - } -#ifdef DEBUG_STACK - LOG("unbounded stack in element order:"); -#endif - for (int k = 0; k < unlimited_stack.size(); k++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "#%d is %s", k, unlimited_stack[k].observe())); -#endif - } -#ifdef DEBUG_STACK - LOG("now popping fresh order:"); -#endif - while (unlimited_stack.size()) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "size %d, content %s", - unlimited_stack.size(), unlimited_stack.top().observe())); -#endif - unlimited_stack.pop(); - } -#ifdef DEBUG_STACK - LOG(""); -#endif - ASSERT_EQUAL(unlimited_stack.pop().value(), common::IS_EMPTY, - "unlimited pop should return empty as expected"); -#ifdef DEBUG_STACK - LOG("\ -----------------------------------------------\n\ -both types of stack exercises were successful.\n\ -----------------------------------------------"); -#endif - } - -#ifdef DEBUG_STACK - LOG("now setting up some simple stacks..."); -#endif - - { -#ifdef DEBUG_STACK - LOG("bounded first..."); -#endif - stack bounder(randomizer.inclusive(30, 80)); -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "length of bounder is max %d, curr %d", bounder.elements(), bounder.size())); -#endif - int max = bounder.elements(); - for (int i = 0; i < max; i++) { - ASSERT_EQUAL(bounder.size(), i, "the bounder size should be in step with loop"); - int byte_array_size = randomizer.inclusive(259, 287); - byte_array to_stuff(byte_array_size); - astring visible(astring::SPRINTF, "entry %d...", i); - visible.stuff((char *)to_stuff.access(), visible.length() + 1); - for (int j = visible.length() + 1; j < to_stuff.length(); j++) - *(to_stuff.access() + j) = '\0'; -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "pushing index %d", i)); -#endif - outcome ret = bounder.push(to_stuff); - ASSERT_EQUAL(ret.value(), common::OKAY, "pushing should not fail in simple test"); - } - ASSERT_EQUAL(bounder.elements(), bounder.size(), "bounder set should see size and max same"); -#ifdef DEBUG_STACK - LOG("inverting:"); -#endif - bounder.invert(); -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "inverted is:\n%s", text_form(bounder).s())); -#endif - while (bounder.size()) bounder.pop(); - } - } -} - -void test_stack::test_stack_with_pointers() -{ - FUNCDEF("test_stack_with_pointers") - for (int qq = 0; qq < test_iterations; qq++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "index %d", qq)); -#endif - stack bounded_stack(3); - stack unlimited_stack(0); - chaos randomizer; - - { -#ifdef DEBUG_STACK - LOG("testing the bounded stack first:"); -#endif - CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the first line")), "first push"); - CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the second line")), "second push"); - CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the final and third")), "third push"); - byte_array *gend = generate_deep("this shouldn't work"); - ASSERT_EQUAL(bounded_stack.push(gend).value(), common::IS_FULL, - "the bounded stack push should catch IS_FULL"); - delete gend; -#ifdef DEBUG_STACK - LOG("pushing worked successfully..."); - LOG("printing the stack in element order"); -#endif - for (int i = 0; i < bounded_stack.size(); i++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "depth %d has: %s", i, bounded_stack[i]->observe())); -#endif - } -#ifdef DEBUG_STACK - LOG("now popping the stack all the way back."); -#endif - int full_size = bounded_stack.size(); - for (int j = 0; j < full_size; j++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "pop %d, stack size is %d, has %s", j+1, bounded_stack.size(), bounded_stack.top()->observe())); -#endif - byte_array *found; - bounded_stack.acquire_pop(found); - ASSERT_TRUE(found, "acquire_pop test should not return nil"); - ASSERT_TRUE(found->observe(), "acquire_pop test should have non-nil observe"); - astring got((char *)found->observe()); - switch (j) { - case 0: - ASSERT_EQUAL(got, astring("the final and third"), - "popping should have right contents at 2"); - break; - case 1: - ASSERT_EQUAL(got, astring("the second line"), - "popping should have right contents at 1"); - break; - case 2: - ASSERT_EQUAL(got, astring("the first line"), - "popping should have right contents at 0"); - break; - } - delete found; - } - ASSERT_EQUAL(bounded_stack.pop().value(), common::IS_EMPTY, - "bounded pop failure in result"); - } - - { -#ifdef DEBUG_STACK - LOG("testing the unbounded stack now:"); -#endif - for (int j = 0; j < 24; j++) { - astring line(astring::SPRINTF, "{posn %d here}", j); - CHECK_STACK_RESULT(unlimited_stack.push(generate_deep(line.s())), "unbound push"); - } -#ifdef DEBUG_STACK - LOG("unbounded stack in element order:"); -#endif - for (int k = 0; k < unlimited_stack.size(); k++) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "#%d is %s", k, unlimited_stack[k]->observe())); -#endif - } -#ifdef DEBUG_STACK - LOG("\nnow popping order:"); -#endif - while (unlimited_stack.size()) { -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "size %d, content %s", unlimited_stack.size(), unlimited_stack.top()->observe())); -#endif - byte_array *to_zap = unlimited_stack.top(); - unlimited_stack.pop(); - delete to_zap; - // okay because the pointer was copied, and the reference from top - // is not being relied on. - } -#ifdef DEBUG_STACK - LOG(""); -#endif - ASSERT_EQUAL(unlimited_stack.pop().value(), common::IS_EMPTY, - "unlimited pop should return empty as expected"); -#ifdef DEBUG_STACK - LOG("\n\ -----------------------------------------------\n\ -both types of stack exercises were successful.\n\ -----------------------------------------------"); -#endif - } - -#ifdef DEBUG_STACK - LOG("now setting up some simple stacks..."); -#endif - - { -#ifdef DEBUG_STACK - LOG("bounded first..."); -#endif - stack bounder(randomizer.inclusive(30, 80)); -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "length of bounder is max %d, curr %d", - bounder.elements(), bounder.size())); -#endif - int max = bounder.elements(); - for (int i = 0; i < max; i++) { - ASSERT_EQUAL(bounder.size(), i, "bounder size should remain in step with loop"); - int byte_array_size = randomizer.inclusive(259, 287); - byte_array *to_stuff = new byte_array(byte_array(byte_array_size)); - astring visible(astring::SPRINTF, "entry %d...", i); - visible.stuff((char *)to_stuff->observe(), visible.length() + 1); - for (int j = visible.length() + 1; j < to_stuff->length(); j++) - *(to_stuff->access() + j) = '\0'; - ASSERT_EQUAL(bounder.push(to_stuff).value(), common::OKAY, - "pushing should not fail in bound simple test"); - } - ASSERT_EQUAL(bounder.elements(), bounder.size(), "bounder set must have size and max agree"); -#ifdef DEBUG_STACK - LOG("inverting:"); -#endif - bounder.invert(); -#ifdef DEBUG_STACK - LOG(astring(astring::SPRINTF, "inverted is:\n%s", text_form(bounder).s())); -#endif - while (bounder.size()) { - byte_array *to_zap = bounder.top(); - bounder.pop(); - delete to_zap; - } - } - } -} - -int test_stack::execute() -{ - test_stack_with_objects(); - test_stack_with_pointers(); - return final_report(); -} - -HOOPLE_MAIN(test_stack, ) - diff --git a/core/library/tests_structures/test_string_table.cpp b/core/library/tests_structures/test_string_table.cpp deleted file mode 100644 index 8eadeda2..00000000 --- a/core/library/tests_structures/test_string_table.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/*****************************************************************************\ -* * -* Name : test_string_table * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -///using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_SYMBOL_TABLE - // uncomment to get lots of noise out of base class. - -//#define DEBUG_STRING_TABLE - // uncomment for noisy version. - -//#define TEST_SIZE_TABLE - // uncomment for testing the size of a string_table. - -//#define OLD_TEST - // uncomment for the older version of symbol table. - -const int test_iterations = 25; - -const int FIND_ITERATIONS = 20; -const int MAXIMUM_RANDOM_ADDS = 50; - -const int TEST_SIZE_TABLE_COUNT = 10000; - // the number of string_table elements we create for checking how - // big one of them is. - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -double time_in_add = 0; -double time_in_find = 0; -double time_in_pack = 0; -double time_in_unpack = 0; -basis::un_int operations = 0; - -class test_string_table : public virtual application_shell, public virtual unit_base -{ -public: - test_string_table() {} - DEFINE_CLASS_NAME("test_string_table"); - - void ADD(string_table &syms, const astring &name, const astring &to_add); - void FIND(const string_table &syms, const astring &name, const astring &to_find); - - void test_1(); - - virtual int execute(); -}; - -////////////// - -void test_string_table::ADD(string_table &syms, const astring &name, const astring &to_add) -{ - FUNCDEF("ADD") -//LOG(astring("add of ") + name + " => " + to_add); - time_stamp start; - outcome added = syms.add(name, to_add); - operations++; - ASSERT_INEQUAL(added.value(), common::EXISTING, "should not already be in table"); - time_stamp end; - time_in_add += end.value() - start.value(); -} - -void test_string_table::FIND(const string_table &syms, const astring &name, const astring &to_find) -{ - FUNCDEF("FIND") - for (int i = 0; i < FIND_ITERATIONS; i++) { - time_stamp start; - astring *found = syms.find(name); - operations++; - time_stamp end; - time_in_find += end.value() - start.value(); - ASSERT_TRUE(found, "should be in table"); - ASSERT_EQUAL(*found, to_find, "string in table should be correct"); - } -} - -////////////// - -void test_string_table::test_1() -{ - FUNCDEF("test_string_table"); -#ifdef TEST_SIZE_TABLE - { - array symso(TEST_SIZE_TABLE_COUNT); - operations++; - guards::alert_message(astring(astring::SPRINTF, "we should effectively " - "have swamped out the size of other code in the program now. the size " - "should represent %d string_table instantiations. take the current " - "memory size (minus maybe 2 megs) and divide by %d and you will have " - "a fairly accurate cost for instantiating a string table. hit a key.", - TEST_SIZE_TABLE_COUNT, TEST_SIZE_TABLE_COUNT)); - #ifdef _CONSOLE - getchar(); - #elif defined(__UNIX__) - getchar(); - #endif - } -#endif - - string_table syms; - string_table new_syms; - string_table newer_syms; - operations += 3; - for (int qq = 0; qq < test_iterations; qq++) { - syms.reset(); // still could be costly. - operations++; -#ifdef DEBUG_STRING_TABLE - LOG(astring(astring::SPRINTF, "index %d", qq)); -#endif - astring freudname("blurgh"); - astring freud("Sigmund Freud was a very freaked dude."); - ADD(syms, freudname, freud); - astring borgname("borg"); - astring borg("You will be assimilated."); - ADD(syms, borgname, borg); - astring xname("X-Men"); - astring x("The great unknown superhero cartoon."); - ADD(syms, xname, x); - astring aname("fleeny-brickle"); - astring a("lallax menick publum."); - ADD(syms, aname, a); - astring axname("ax"); - astring ax("Lizzy Borden has a very large hatchet."); - ADD(syms, axname, ax); - astring bloinkname("urg."); - astring bloink("this is a short and stupid string"); - ADD(syms, bloinkname, bloink); - astring faxname("fax"); - astring fax("alligators in my teacup."); - ADD(syms, faxname, fax); - astring zname("eagle ovaries"); - astring z("malfeasors beware"); - ADD(syms, zname, z); - - FIND(syms, freudname, freud); - FIND(syms, borgname, borg); - FIND(syms, xname, x); - FIND(syms, aname, a); - FIND(syms, axname, ax); - FIND(syms, bloinkname, bloink); - FIND(syms, faxname, fax); - FIND(syms, zname, z); - - astring name; - astring content; - for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { - name = string_manipulation::make_random_name(40, 108); - content = string_manipulation::make_random_name(300, 1000); - ADD(syms, name, content); - FIND(syms, name, content); - } - - // test copying of the string tables. - string_table chronos = syms; - operations++; - { - string_table mary = syms; - operations++; - string_table june = mary; - operations++; - ASSERT_TRUE(mary == syms, "copy test should compare properly"); - operations++; - } - ASSERT_TRUE(syms == chronos, "copy test original should not be harmed"); - operations++; - - { - // test the bug we think we found in the operator =. - string_table fred; - fred.add("urp", "rarp"); - fred.add("hyurgh", "ralph"); - string_table med; - med.add("urp", "rarp"); - med.add("hyurgh", "ralph"); - fred = med; // the deadly assignment. - fred = med; // the deadly assignment. - fred = med; // the deadly assignment. - fred = med; // the deadly assignment. - fred.add("urp", "rarp"); - fred.add("gurp", "flaarp"); // a new entry. - astring *urp = fred.find("urp"); - astring *hyurgh = fred.find("hyurgh"); - ASSERT_TRUE(urp, "urp should not go missing"); - ASSERT_TRUE(hyurgh, "hyurgh should not go missing"); -#ifdef DEBUG_STRING_TABLE - LOG(astring("got urp as ") + (urp? *urp : "empty!!!!")); - LOG(astring("got hyurgh as ") + (hyurgh? *hyurgh : "empty!!!!")); -#endif - astring urp_val = fred[0]; - // AH HA! this one finds it. accessing via bracket or other methods - // that use the internal get() method will fail. - // if there is no outright crash, then the bug is gone. -#ifdef DEBUG_STRING_TABLE - LOG(astring("got urp_val as ") + (urp_val.t()? urp_val : "empty!!!!")); -#endif - } - -#ifdef DEBUG_STRING_TABLE -//// LOG(astring(astring::SPRINTF,"This is the symbol table before any manipulation\n%s", syms.text_form())); - LOG("now packing the symbol table..."); -#endif - byte_array packed_form; - time_stamp start; - syms.pack(packed_form); - operations++; - time_stamp end; - time_in_pack += end.value() - start.value(); -#ifdef DEBUG_STRING_TABLE - LOG("now unpacking from packed form"); -#endif - start.reset(); - ASSERT_TRUE(new_syms.unpack(packed_form), "unpack test should succeed in unpacking"); - operations++; - end.reset(); // click, off. - time_in_unpack += end.value() - start.value(); - -#ifdef DEBUG_STRING_TABLE -/// LOG(astring(astring::SPRINTF, "unpacked form has:\n%s", new_syms->text_form().s())); -#endif - ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables should be same"); - operations++; - -#ifdef DEBUG_STRING_TABLE - LOG("now deleting old symbol table..."); -#endif - -#ifdef DEBUG_STRING_TABLE -/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", new_syms->text_form().s())); - LOG("packing the symbol table again..."); -#endif - byte_array packed_again(0); - start.reset(); // click, on. - new_syms.pack(packed_again); - operations++; - end.reset(); // click, off. - time_in_pack += end.value() - start.value(); -#ifdef DEBUG_STRING_TABLE - LOG("now unpacking from packed form again..."); -#endif - start = time_stamp(); - newer_syms.unpack(packed_again); - operations++; - end = time_stamp(); - time_in_unpack += end.value() - start.value(); -#ifdef DEBUG_STRING_TABLE - LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping " - "it:\n%s", newer_syms.text_form().s())); -#endif - ASSERT_TRUE(new_syms == newer_syms, "unpacked test should not be different symbol tables"); - operations++; - -#ifdef DEBUG_STRING_TABLE - LOG("now deleting new and newer symbol table..."); -#endif - } -} - -////////////// - -int test_string_table::execute() -{ -#ifdef DEBUG_STRING_TABLE - LOG(astring("starting test: ") + time_stamp::notarize(false)); -#endif - test_1(); -#ifdef DEBUG_STRING_TABLE - LOG(astring("done test: ") + time_stamp::notarize(false)); - LOG(astring(astring::SPRINTF, "time in add=%f", time_in_add)); - LOG(astring(astring::SPRINTF, "time in find=%f", time_in_find)); - LOG(astring(astring::SPRINTF, "time in pack=%f", time_in_pack)); - LOG(astring(astring::SPRINTF, "time in unpack=%f", time_in_unpack)); - LOG(astring(astring::SPRINTF, "total operations=%u", operations)); -#endif - - return final_report(); -} - -HOOPLE_MAIN(test_string_table, ) - diff --git a/core/library/tests_structures/test_symbol_table.cpp b/core/library/tests_structures/test_symbol_table.cpp deleted file mode 100644 index 22535a8f..00000000 --- a/core/library/tests_structures/test_symbol_table.cpp +++ /dev/null @@ -1,591 +0,0 @@ -/* -* Name : test_symbol_table -* Author : Chris Koeritz -** -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -//#define DEBUG_SYMBOL_TABLE - // uncomment for noisy version. - -const int test_iterations = 4; - -const int FIND_ITERATIONS = 10; -const int MAXIMUM_RANDOM_ADDS = 20; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -//#define OLD_TEST - // uncomment for the older version of symbol table. - -double time_in_add = 0; -double time_in_dep_find = 0; -double time_in_new_find = 0; -double time_in_pack = 0; -double time_in_unpack = 0; -double time_in_copy = 0; - -////////////// - -class my_table_def : virtual public hoople_standard, virtual public symbol_table -///, virtual public equalizable -{ -public: - DEFINE_CLASS_NAME("my_table_def"); - - virtual void text_form(base_string &state_fill) const { - state_fill.assign(astring(class_name()) + ": uhhh not really implemented"); - } - - virtual bool equal_to(const equalizable &s2) const { - const my_table_def *to_compare = dynamic_cast(&s2); - if (!to_compare) return false; - if (symbols() != to_compare->symbols()) return false; - for (int i = 0; i < symbols(); i++) { - if (name(i) != to_compare->name(i)) return false; - if (operator [](i) != (*to_compare)[i]) return false; - } - return true; - } -}; - -////////////// - -// jethro is a simple object for containment below. -class jethro -{ -public: - astring _truck; - - bool operator ==(const jethro &tc) const { return tc._truck == _truck; } - bool operator !=(const jethro &tc) const { return !(*this == tc); } -}; - -////////////// - -// the test_content object is an object with proper copy constructor -// and assignment operator that also has deep contents. -class test_content : public packable -{ -public: - int _q; - astring _ted; - astring _jed; - int_array _ned; - matrix _med; - - test_content() : _q(9) {} - - test_content(const astring &ted, const astring &jed) : _q(4), _ted(ted), - _jed(jed) {} - -//hmmm: pack and unpack don't do everything. - void pack(byte_array &packed_form) const { - attach(packed_form, _q); - _ted.pack(packed_form); - _jed.pack(packed_form); - } - int packed_size() const { - return sizeof(_q) + _ted.packed_size() + _jed.packed_size(); - } - bool unpack(byte_array &packed_form) { - if (!detach(packed_form, _q)) return false; - if (!_ted.unpack(packed_form)) return false; - if (!_jed.unpack(packed_form)) return false; - return true; - } - - bool operator ==(const test_content &tc) const { - if (tc._q != _q) return false; - if (tc._ted != _ted) return false; - if (tc._jed != _jed) return false; - if (tc._ned.length() != _ned.length()) return false; - for (int i = 0; i < _ned.length(); i++) - if (tc._ned[i] != _ned[i]) return false; - - if (tc._med.rows() != _med.rows()) return false; - if (tc._med.columns() != _med.columns()) return false; - for (int c = 0; c < _med.columns(); c++) - for (int r = 0; r < _med.rows(); r++) - if (tc._med.get(r, c) != _med.get(r, c)) return false; - - return true; - } - bool operator !=(const test_content &tc) const { return !operator ==(tc); } - - operator int() const { return _q; } -}; - -////////////// - -class second_table_def : virtual public hoople_standard, virtual public symbol_table -{ -public: - DEFINE_CLASS_NAME("second_table_def") - - virtual void text_form(base_string &state_fill) const { - state_fill.assign(astring(class_name()) + ": uhhh not really implemented"); - } - - virtual bool equal_to(const equalizable &s2) const { - const second_table_def *to_compare = dynamic_cast(&s2); - if (symbols() != to_compare->symbols()) return false; - for (int i = 0; i < symbols(); i++) { - if ((*this)[i] != (*to_compare)[i]) return false; - } - return true; - } -}; - -////////////// - -class test_symbol_table : public virtual application_shell, public virtual unit_base -{ -public: - test_symbol_table() {} - DEFINE_CLASS_NAME("test_symbol_table"); - - void test_1(); - - virtual int execute(); - - void ADD(my_table_def &syms, const astring &name, const astring &to_add); - void FIND(const my_table_def &syms, const astring &name, const astring &to_find); - - void ADD2(second_table_def &syms, const astring &name, const test_content &to_add); - - void pack(byte_array &packed_form, const my_table_def &to_pack); - bool unpack(byte_array &packed_form, my_table_def &to_unpack); - - void pack(byte_array &packed_form, const second_table_def &to_pack); - bool unpack(byte_array &packed_form, second_table_def &to_unpack); - - void test_byte_table(); - void test_tc_table(); -}; - -////////////// - -void test_symbol_table::ADD(my_table_def &syms, const astring &name, const astring &to_add) -{ - FUNCDEF("ADD") - byte_array to_stuff(to_add.length() + 1, (abyte *)to_add.s()); - time_stamp start; - outcome added = syms.add(name, to_stuff); - ASSERT_EQUAL(added.value(), common::IS_NEW, "should not already be in table"); - time_stamp end; - time_in_add += end.value() - start.value(); - start.reset(); -#ifdef OLD_TEST - int indy = syms.find(name); - ASSERT_FALSE(negative(indy), "should be in table after add"); - end.reset(); - time_in_dep_find += end.value() - start.value(); - const byte_array *found = &syms[indy]; -#else - byte_array *found = syms.find(name); - ASSERT_TRUE(found, "really should be in table after add"); - end.reset(); - time_in_new_find += end.value() - start.value(); -#endif - ASSERT_EQUAL(*found, to_stuff, "value should be right in table after add"); -} - -void test_symbol_table::FIND(const my_table_def &syms, const astring &name, const astring &to_add) -{ - FUNCDEF("FIND") - byte_array to_stuff(to_add.length() + 1, (abyte *)to_add.s()); - for (int i = 0; i < FIND_ITERATIONS; i++) { - time_stamp start; -#ifdef OLD_TEST - // double the calls so we roughly match the other test. - int indy = syms.find(name); - ASSERT_FALSE(negative(indy), "should locate item in table"); - indy = syms.find(name); - ASSERT_FALSE(negative(indy), "second find should locate item in table"); - byte_array *found = &syms[indy]; - time_stamp end; - time_in_dep_find += end.value() - start.value(); -#else - int indy = syms.dep_find(name); - ASSERT_FALSE(negative(indy), "should locate item in table (dep_find)"); - time_stamp end; - time_in_dep_find += end.value() - start.value(); - start.reset(); - byte_array *found = syms.find(name); - ASSERT_TRUE(found, "second find should see item in table (new_find)"); - end.reset(); - time_in_new_find += end.value() - start.value(); -#endif - } -} - -void test_symbol_table::pack(byte_array &packed_form, const my_table_def &to_pack) -{ - attach(packed_form, to_pack.symbols()); - astring name; - byte_array content; - for (int i = 0; i < to_pack.symbols(); i++) { - to_pack.retrieve(i, name, content); - name.pack(packed_form); - attach(packed_form, content); - } -} - -bool test_symbol_table::unpack(byte_array &packed_form, my_table_def &to_unpack) -{ - to_unpack.reset(); - int syms = 0; - if (!detach(packed_form, syms)) return false; - astring name; - byte_array chunk; - for (int i = 0; i < syms; i++) { - if (!name.unpack(packed_form)) return false; - if (!detach(packed_form, chunk)) return false; - ADD(to_unpack, name, (char *)chunk.observe()); - } - return true; -} - -void test_symbol_table::pack(byte_array &packed_form, const second_table_def &to_pack) -{ - attach(packed_form, to_pack.symbols()); - astring name; - test_content content; - for (int i = 0; i < to_pack.symbols(); i++) { - to_pack.retrieve(i, name, content); - name.pack(packed_form); - content.pack(packed_form); - } -} - -bool test_symbol_table::unpack(byte_array &packed_form, second_table_def &to_unpack) -{ - to_unpack.reset(); - int syms = 0; - if (!detach(packed_form, syms)) return false; - astring name; - test_content chunk; - for (int i = 0; i < syms; i++) { - if (!name.unpack(packed_form)) return false; - if (!chunk.unpack(packed_form)) return false; - to_unpack.add(name, chunk); - } - return true; -} - -////////////// - -my_table_def creatapose() -{ - my_table_def to_return; - astring name; - astring content; - for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { - name = string_manipulation::make_random_name(40, 108); - content = string_manipulation::make_random_name(300, 1000); - byte_array to_stuff(content.length() + 1, (abyte *)content.s()); - to_return.add(name, to_stuff); - } - return to_return; -} - -////////////// - -void test_symbol_table::test_byte_table() -{ - FUNCDEF("test_byte_table") - my_table_def syms; - my_table_def new_syms; - my_table_def newer_syms; - for (int qq = 0; qq < test_iterations; qq++) { - syms.reset(); // still could be costly. -#ifdef DEBUG_SYMBOL_TABLE - LOG(astring(astring::SPRINTF, "index %d", qq)); -#endif - astring freudname("blurgh"); - astring freud("Sigmund Freud was a very freaked dude."); - ADD(syms, freudname, freud); - astring borgname("borg"); - astring borg("You will be assimilated."); - ADD(syms, borgname, borg); - astring xname("X-Men"); - astring x("The great unknown superhero cartoon."); - ADD(syms, xname, x); - astring aname("fleeny-brickle"); - astring a("lallax menick publum."); - ADD(syms, aname, a); - astring axname("ax"); - astring ax("Lizzy Borden has a very large hatchet."); - ADD(syms, axname, ax); - astring bloinkname("urg."); - astring bloink("this is a short and stupid string"); - ADD(syms, bloinkname, bloink); - astring faxname("fax"); - astring fax("alligators in my teacup."); - ADD(syms, faxname, fax); - astring zname("eagle ovaries"); - astring z("malfeasors beware"); - ADD(syms, zname, z); - - FIND(syms, freudname, freud); - FIND(syms, borgname, borg); - FIND(syms, xname, x); - FIND(syms, aname, a); - FIND(syms, axname, ax); - FIND(syms, bloinkname, bloink); - FIND(syms, faxname, fax); - FIND(syms, zname, z); - - astring name; - astring content; - for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { - name = string_manipulation::make_random_name(40, 108); - content = string_manipulation::make_random_name(300, 1000); - ADD(syms, name, content); - FIND(syms, name, content); - } - - // test copying the table. - time_stamp start; // click, on. - my_table_def copy1(syms); - { - my_table_def joe(copy1); - my_table_def joe2 = joe; - ASSERT_EQUAL(joe2, joe, "copy test A: symbol tables should be same"); - my_table_def joe3 = creatapose(); // on stack. - my_table_def joe4 = joe3; - my_table_def joe5 = joe4; - ASSERT_EQUAL(joe5, joe3, "copy test A2: symbol tables should be same"); - } - ASSERT_FALSE(! (syms == copy1), "copy test B: symbol tables should be same still"); - time_stamp end; - time_in_copy += end.value() - start.value(); - -#ifdef DEBUG_SYMBOL_TABLE -//// LOG(astring(astring::SPRINTF,"This is the symbol table before any manipulation\n%s", syms.text_form())); - LOG("now packing the symbol table..."); -#endif - -#ifdef DEBUG_SYMBOL_TABLE - LOG("now unpacking from packed form"); -#endif - byte_array packed_form; - pack(packed_form, syms); - ASSERT_TRUE(unpack(packed_form, new_syms), "unpack test should not fail to unpack"); - -#ifdef DEBUG_SYMBOL_TABLE -/// LOG(astring(astring::SPRINTF, "unpacked form has:\n%s", new_syms->text_form().s())); -#endif - ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables must be equal"); - -#ifdef DEBUG_SYMBOL_TABLE -/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", new_syms->text_form().s())); - LOG("packing the symbol table again..."); -#endif - byte_array packed_again(0); - start.reset(); // click, on. - pack(packed_again, new_syms); - end.reset(); // click, off. - time_in_pack += end.value() - start.value(); -#ifdef DEBUG_SYMBOL_TABLE - LOG("now unpacking from packed form again..."); -#endif - start = time_stamp(); - ASSERT_TRUE(unpack(packed_again, newer_syms), "newer unpacking should working be"); - end = time_stamp(); - time_in_unpack += end.value() - start.value(); -#ifdef DEBUG_SYMBOL_TABLE -/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", newer_syms->text_form().s())); -#endif - ASSERT_EQUAL(new_syms, newer_syms, - "unpacked test these just aren't getting it but should be same"); - } -} - -////////////// - -void test_symbol_table::ADD2(second_table_def &syms, const astring &name, - const test_content &to_add) -{ - FUNCDEF("ADD2") - time_stamp start; - outcome added = syms.add(name, to_add); - ASSERT_EQUAL(added.value(), common::IS_NEW, "new item should not already be in table"); - time_stamp end; - time_in_add += end.value() - start.value(); - start = time_stamp(); // reset start. -#ifdef OLD_TEST - int indy = syms.find(name); - ASSERT_FALSE(negative(indy), "item should be found after add"); - // refind to balance timing. - indy = syms.find(name); - ASSERT_FALSE(negative(indy), "item should be found after second add"); - end = time_stamp(); // reset end. - time_in_dep_find += end.value() - start.value(); -#else - int indy = syms.dep_find(name); - ASSERT_FALSE(negative(indy), "finding item after add should work"); - end = time_stamp(); // reset end. - time_in_dep_find += end.value() - start.value(); - start = time_stamp(); - test_content *found = syms.find(name); - ASSERT_TRUE(found, "item shouldn't be nil that we found"); - end = time_stamp(); // reset end. - time_in_new_find += end.value() - start.value(); -#endif - astring name_out; - test_content content_out; - if (syms.retrieve(indy, name_out, content_out) != common::OKAY) { - ASSERT_EQUAL(name_out, name, "name should be correct after retrieve"); - ASSERT_EQUAL(content_out, to_add, "content should be correct after retrieve"); - } -} - -////////////// - -void test_symbol_table::test_tc_table() -{ - FUNCDEF("test_tc_table") - second_table_def syms; - second_table_def new_syms; - second_table_def newer_syms; - for (int qq = 0; qq < test_iterations; qq++) { - syms.reset(); -#ifdef DEBUG_SYMBOL_TABLE - LOG(astring(astring::SPRINTF, "index %d", qq)); -#endif - astring freudname("blurgh"); - test_content freud("Sigmund Freud was a very freaked dude.", "flutenorf"); - ADD2(syms, freudname, freud); - astring borgname("borg"); - test_content borg("You will be assimilated.", "alabaster"); - ADD2(syms, borgname, borg); - astring xname("X-Men"); - test_content x("The great unknown superhero cartoon.", "somnambulist"); - ADD2(syms, xname, x); - astring aname("fleeny-brickle"); - test_content a("lallax menick publum.", "aglos bagnort pavlod"); - ADD2(syms, aname, a); - astring axname("ax"); - test_content ax("Lizzy Borden has a very large hatchet.", "chop"); - ADD2(syms, axname, ax); - astring bloinkname("urg."); - test_content bloink("this is a short and stupid string", "not that short"); - ADD2(syms, bloinkname, bloink); - astring faxname("fax"); - test_content fax("alligators in my teacup.", "lake placid"); - ADD2(syms, faxname, fax); - astring zname("eagle ovaries"); - test_content z("malfeasors beware", "endangered"); - ADD2(syms, zname, z); - - // test copying the table. - time_stamp start; - second_table_def copy1(syms); - { - second_table_def joe(copy1); - second_table_def joe2 = joe; - ASSERT_EQUAL(joe2, joe, "copy test C: should have same symbol tables"); - } - ASSERT_FALSE(! (syms == copy1), "copy test D: symbol tables shouldn't be different"); - time_stamp end; - time_in_copy += end.value() - start.value(); - -#ifdef DEBUG_SYMBOL_TABLE - astring texto; - syms.text_form(texto); - LOG(astring("This is the symbol table before any manipulation\n") + texto); - LOG("now packing the symbol table..."); -#endif - -#ifdef DEBUG_SYMBOL_TABLE - LOG("now unpacking from packed form"); -#endif - byte_array packed_form; - pack(packed_form, syms); - ASSERT_TRUE(unpack(packed_form, new_syms), "crikey all these unpacks should work"); -#ifdef DEBUG_SYMBOL_TABLE - new_syms.text_form(texto); - LOG(astring("unpacked form has:\n") + texto); -#endif - ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables should be equivalent"); - -#ifdef DEBUG_SYMBOL_TABLE - new_syms.text_form(texto); - LOG(astring("got the unpacked form, and dumping it:\n") + texto); - LOG("packing the symbol table again..."); -#endif - byte_array packed_again(0); - pack(packed_again, new_syms); -#ifdef DEBUG_SYMBOL_TABLE - LOG("now unpacking from packed form again..."); -#endif - ASSERT_TRUE(unpack(packed_again, newer_syms), "unpacking should get back the goods"); -#ifdef DEBUG_SYMBOL_TABLE - newer_syms.text_form(texto); - LOG(astring("got the unpacked form, and dumping it:\n") + texto); -#endif - ASSERT_FALSE(! (new_syms == newer_syms), "unpacked test symbol tables should stay same"); - } -} - -////////////// - -int test_symbol_table::execute() -{ -#ifdef DEBUG_SYMBOL_TABLE - LOG(astring("starting test 1: ") + time_stamp::notarize(false)); -#endif - test_byte_table(); -#ifdef DEBUG_SYMBOL_TABLE - LOG(astring("done test 1: ") + time_stamp::notarize(false)); - LOG(astring("starting test 2: ") + time_stamp::notarize(false)); -#endif - - test_tc_table(); -#ifdef DEBUG_SYMBOL_TABLE - LOG(astring("done test 2: ") + time_stamp::notarize(false)); - LOG(astring(astring::SPRINTF, "time in add=%f", time_in_add)); - LOG(astring(astring::SPRINTF, "time in dep_find=%f", time_in_dep_find)); - LOG(astring(astring::SPRINTF, "time in new_find=%f", time_in_new_find)); - LOG(astring(astring::SPRINTF, "time in pack=%f", time_in_pack)); - LOG(astring(astring::SPRINTF, "time in unpack=%f", time_in_unpack)); -#endif - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_symbol_table, ) - diff --git a/core/library/tests_structures/test_unique_id.cpp b/core/library/tests_structures/test_unique_id.cpp deleted file mode 100644 index d86f6b77..00000000 --- a/core/library/tests_structures/test_unique_id.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/*****************************************************************************\ -* * -* Name : t_unique_id * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DEBUG_STACK - #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) -#endif - -using namespace application; -using namespace basis; -///using namespace configuration; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#include -#include - -////////////// - -class test_unique_id : public virtual unit_base, public virtual application_shell -{ -public: - test_unique_id() {} - DEFINE_CLASS_NAME("test_unique_id"); - int execute(); -}; - -HOOPLE_MAIN(test_unique_id, ); - -////////////// - -int test_unique_id::execute() -{ - FUNCDEF("execute"); - unique_int ted(25); - unique_int jed; - - ASSERT_TRUE(ted.raw_id(), "testing non-zero should not claim was zero"); - ASSERT_TRUE(!jed, "testing zero should claim was zero"); - ASSERT_TRUE(!!ted, "testing non-zero doubled should not claim was zero"); - ASSERT_TRUE(!!!jed, "testing zero doubled should claim was zero"); - - unique_int med(25); - unique_int fed(0); - - ASSERT_EQUAL(med.raw_id(), ted.raw_id(), "testing equality 1 should have correct result"); - ASSERT_EQUAL(fed.raw_id(), jed.raw_id(), "testing equality 2 should have correct result"); - ASSERT_INEQUAL(med.raw_id(), jed.raw_id(), "testing equality 3 should have correct result"); - ASSERT_INEQUAL(fed.raw_id(), ted.raw_id(), "testing equality 4 should have correct result"); - - ASSERT_FALSE(med != ted, "equality operator 1 should have correct result"); - ASSERT_FALSE(fed != jed, "equality operator 2 should have correct result"); - ASSERT_FALSE(med == jed, "equality operator 3 should have correct result"); - ASSERT_FALSE(fed == ted, "equality operator 4 should have correct result"); - - return final_report(); -} - diff --git a/core/library/tests_structures/test_version.cpp b/core/library/tests_structures/test_version.cpp deleted file mode 100644 index 8a68ffab..00000000 --- a/core/library/tests_structures/test_version.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* -* Name : test_version -* Author : Chris Koeritz -** -* Copyright (c) 2009-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -class test_version : public virtual unit_base, virtual public application_shell -{ -public: - test_version() : application_shell() {} - DEFINE_CLASS_NAME("test_version"); - virtual int execute(); -}; - -int test_version::execute() -{ - FUNCDEF("execute"); - - version v1(5, 6, 138); - version v2(5, 7, 108); - ASSERT_TRUE((v1 < v2), "compare v1 < v2 should work properly"); - ASSERT_FALSE(v1 > v2, "compare v1 > v2 should work properly"); - ASSERT_FALSE(v1 == v2, "compare v1 == v2 should work properly"); - ASSERT_TRUE((v1 != v2), "compare v1 != v2 should work properly"); - - version v3(4, 6, 180); - ASSERT_TRUE((v3 < v1), "compare v3 < v1 should work properly"); - ASSERT_FALSE(v3 > v1, "compare v3 > v1 should work properly"); - ASSERT_FALSE(v3 == v1, "compare v3 == v1 should work properly"); - ASSERT_TRUE((v3 != v1), "compare v3 != v1 should work properly"); - ASSERT_TRUE((v3 < v2), "compare v3 < v2 should work properly"); - ASSERT_FALSE(v3 > v2, "compare v3 > v2 should work properly"); - ASSERT_FALSE(v3 == v2, "compare v3 == v2 should work properly"); - ASSERT_TRUE((v3 != v2), "compare v3 != v2 should work properly"); - ASSERT_FALSE(v1 < v3, "compare v1 < v3 should work properly"); - ASSERT_TRUE((v1 > v3), "compare v1 > v3 should work properly"); - ASSERT_FALSE(v1 == v3, "compare v1 == v3 should work properly"); - ASSERT_TRUE((v1 != v3), "compare v1 != v3 should work properly"); - ASSERT_FALSE(v2 < v3, "compare v2 < v3 should work properly"); - ASSERT_TRUE((v2 > v3), "compare v2 > v3 should work properly"); - ASSERT_FALSE(v2 == v3, "compare v2 == v3 should work properly"); - ASSERT_TRUE((v2 != v3), "compare v2 != v3 should work properly"); - - version v4(4, 6, 180); - ASSERT_FALSE(v3 < v4, "compare v3 < v4 should work properly"); - ASSERT_FALSE(v3 > v4, "compare v3 > v4 should work properly"); - ASSERT_TRUE((v3 == v4), "compare v3 == v4 should work properly"); - ASSERT_FALSE(v3 != v4, "compare v3 != v4 should work properly"); - ASSERT_FALSE(v4 < v3, "compare v4 < v3 should work properly"); - ASSERT_FALSE(v4 > v3, "compare v4 > v3 should work properly"); - ASSERT_TRUE((v4 == v3), "compare v4 == v3 should work properly"); - ASSERT_FALSE(v4 != v3, "compare v4 != v3 should work properly"); - - return final_report(); -} - -HOOPLE_MAIN(test_version, ) - diff --git a/core/library/tests_textual/df_1.csv b/core/library/tests_textual/df_1.csv deleted file mode 100644 index 9aa58dfc..00000000 --- a/core/library/tests_textual/df_1.csv +++ /dev/null @@ -1,112 +0,0 @@ -"1", "OK", "STRING", "Row0", -"2", "OK", "INT16", "722", -"3", "OK", "INT16", "363", -"4", "OK", "INT16", "992", -"5", "OK", "INT16", "665", -"6", "OK", "INT16", "454", -"7", "OK", "INT16", "431", -"8", "OK", "INT16", "460", -"101", "OK", "STRING", "Row1", -"102", "OK", "INT16", "805", -"103", "OK", "INT16", "50", -"104", "OK", "INT16", "715", -"105", "OK", "INT16", "992", -"106", "OK", "INT16", "449", -"107", "OK", "INT16", "430", -"108", "OK", "INT16", "463", -"201", "OK", "STRING", "Row2", -"202", "OK", "INT16", "84", -"203", "OK", "INT16", "957", -"204", "OK", "INT16", "666", -"205", "OK", "INT16", "379", -"206", "OK", "INT16", "616", -"207", "OK", "INT16", "305", -"208", "OK", "INT16", "214", -"301", "OK", "STRING", "Row3", -"302", "OK", "INT16", "479", -"303", "OK", "INT16", "620", -"304", "OK", "INT16", "621", -"305", "OK", "INT16", "146", -"306", "OK", "INT16", "699", -"307", "OK", "INT16", "440", -"308", "OK", "INT16", "577", -"401", "OK", "STRING", "Row4", -"402", "OK", "INT16", "150", -"403", "OK", "INT16", "287", -"404", "OK", "INT16", "108", -"405", "OK", "INT16", "69", -"406", "OK", "INT16", "298", -"407", "OK", "INT16", "947", -"408", "OK", "INT16", "504", -"501", "OK", "STRING", "Row5", -"502", "OK", "INT16", "305", -"503", "OK", "INT16", "46", -"504", "OK", "INT16", "695", -"505", "OK", "INT16", "892", -"506", "OK", "INT16", "645", -"507", "OK", "INT16", "698", -"508", "OK", "INT16", "467", -"601", "OK", "STRING", "Row6", -"602", "OK", "INT16", "384", -"603", "OK", "INT16", "65", -"604", "OK", "INT16", "710", -"605", "OK", "INT16", "279", -"606", "OK", "INT16", "12", -"607", "OK", "INT16", "157", -"608", "OK", "INT16", "386", -"701", "OK", "STRING", "Row7", -"702", "OK", "INT16", "299", -"703", "OK", "INT16", "656", -"704", "OK", "INT16", "665", -"705", "OK", "INT16", "950", -"706", "OK", "INT16", "79", -"707", "OK", "INT16", "44", -"708", "OK", "INT16", "413", -"801", "OK", "STRING", "Row8", -"802", "OK", "INT16", "938", -"803", "OK", "INT16", "19", -"804", "OK", "INT16", "592", -"805", "OK", "INT16", "145", -"806", "OK", "INT16", "574", -"807", "OK", "INT16", "671", -"808", "OK", "INT16", "564", -"901", "OK", "STRING", "Row9", -"902", "OK", "INT16", "221", -"903", "OK", "INT16", "162", -"904", "OK", "INT16", "203", -"905", "OK", "INT16", "304", -"906", "OK", "INT16", "617", -"907", "OK", "INT16", "158", -"908", "OK", "INT16", "631", -"1001", "OK", "STRING", "Row10", -"1002", "OK", "INT16", "844", -"1003", "OK", "INT16", "157", -"1004", "OK", "INT16", "634", -"1005", "OK", "INT16", "123", -"1006", "OK", "INT16", "64", -"1007", "OK", "INT16", "585", -"1008", "OK", "INT16", "510", -"1101", "OK", "STRING", "Row11", -"1102", "OK", "INT16", "999", -"1103", "OK", "INT16", "228", -"1104", "OK", "INT16", "261", -"1105", "OK", "INT16", "394", -"1106", "OK", "INT16", "123", -"1107", "OK", "INT16", "216", -"1108", "OK", "INT16", "161", -"1201", "OK", "STRING", "Row12", -"1202", "OK", "INT16", "206", -"1203", "OK", "INT16", "583", -"1204", "OK", "INT16", "524", -"1205", "OK", "INT16", "877", -"1206", "OK", "INT16", "18", -"1207", "OK", "INT16", "11", -"1208", "OK", "INT16", "800", -"1301", "OK", "STRING", "Row13", -"1302", "OK", "INT16", "385", -"1303", "OK", "INT16", "286", -"1304", "OK", "INT16", "727", -"1305", "OK", "INT16", "540", -"1306", "OK", "INT16", "277", -"1307", "OK", "INT16", "314", -"1308", "OK", "INT16", "363", diff --git a/core/library/tests_textual/makefile b/core/library/tests_textual/makefile deleted file mode 100644 index 0fcaf2d4..00000000 --- a/core/library/tests_textual/makefile +++ /dev/null @@ -1,15 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_textual -TYPE = test -LAST_TARGETS = copy_datafile -TARGETS = test_byte_format.exe test_parse_csv.exe test_splitter.exe test_xml_generator.exe -LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ - structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - -copy_datafile: - $(HIDER)cp -f df_1.csv $(EXECUTABLE_DIR) - diff --git a/core/library/tests_textual/test_byte_format.cpp b/core/library/tests_textual/test_byte_format.cpp deleted file mode 100644 index 0895cc44..00000000 --- a/core/library/tests_textual/test_byte_format.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -* Name : test_byte_format -* Author : Chris Koeritz -* Purpose: Puts the byte formatting utilities through their paces. -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) - -class test_byte_format : virtual public unit_base, virtual public application_shell -{ -public: - test_byte_format() {} - DEFINE_CLASS_NAME("test_byte_format"); - int execute(); -}; - -int test_byte_format::execute() -{ - FUNCDEF("execute"); - { - // test out the byte shifting functions on a simple known array. - byte_array source; - source += 0x23; source += 0x5f; source += 0xa8; source += 0x2d; - source += 0xe2; source += 0x61; source += 0x90; source += 0x2d; - source += 0xf2; source += 0x38; - astring shifty; - byte_formatter::bytes_to_shifted_string(source, shifty); -// LOG(a_sprintf("A: shifted our %d bytes into: ", source.length()) -// + shifty); - byte_array source_copy; - byte_formatter::shifted_string_to_bytes(shifty, source_copy); - ASSERT_EQUAL(source_copy, source, "A: shifting corrupted the bytes."); - } - - { - // test out the byte shifting functions on a random array. - for (int run = 0; run < 100; run++) { - byte_array source; - int size = randomizer().inclusive(1, 20923); - for (int i = 0; i < size; i++) { - source += abyte(randomizer().inclusive(0, 255)); - } - astring shifty; - byte_formatter::bytes_to_shifted_string(source, shifty); -// LOG(a_sprintf("B: shifted our %d bytes into: ", source.length()) -// + shifty); - byte_array source_copy; - byte_formatter::shifted_string_to_bytes(shifty, source_copy); - ASSERT_EQUAL(source_copy, source, "B: shifting corrupted the bytes."); - } - } - - { - astring burf("alia bodalia petunia"); - astring dump(byte_formatter::text_dump((abyte *)burf.s(), burf.length() + 1)); -//doofus! make this compare the output with expectations. - LOG("dumped form is:"); - LOG(""); - LOG(dump); - } - { - abyte fodder[] = { 0x83, 0x0d, 0x93, 0x21, 0x82, 0xfe, 0xef, 0xdc, 0xb9, - 0xa9, 0x21, 0x54, 0x83, 0x38, 0x65, 0x59, 0x99, 0xff, 0x00, 0xa0, - 0x29, 0x03 }; - byte_array fred(sizeof(fodder), fodder); - astring as_text1; - byte_formatter::bytes_to_string(fred, as_text1, true); - LOG(astring("got string #1 of: ") + as_text1); - astring as_text2; - byte_formatter::bytes_to_string(fred, as_text2, false); - LOG(astring("got string #2 of: ") + as_text2); - byte_array convert1; - byte_formatter::string_to_bytes(as_text1, convert1); - byte_array convert2; - byte_formatter::string_to_bytes(as_text2, convert2); - ASSERT_EQUAL(fred, convert1, "first abyte conversion: failed due to inequality"); - ASSERT_TRUE(fred == convert2, "second abyte conversion: failed due to inequality"); - // now a harder test. - astring as_text3("muggulo x83d x93, x21, x82, xfe, xef, xdc, xb9, " - "xa9, x21, x54, x83, x38, x65, x59, x99, xff, x00a0, x293"); - byte_array harder_convert1; - byte_formatter::string_to_bytes(as_text3, harder_convert1); -astring back3; -byte_formatter::bytes_to_string(harder_convert1, back3); -LOG(astring("got third: ") + back3); - - astring as_text4("muggulo x83d x93, x21, x82, xfe, xef, xdc, xb9, " - "xa9, x21, x54, x83, x38, x65, x59, x99, xff, x00a0, x293gikkor"); - byte_array harder_convert2; - byte_formatter::string_to_bytes(as_text4, harder_convert2); -astring back4; -byte_formatter::bytes_to_string(harder_convert2, back4); -LOG(astring("got fourth: ") + back4); - ASSERT_EQUAL(fred, harder_convert1, "third abyte conversion: failed due to inequality"); - ASSERT_EQUAL(fred, harder_convert2, "fourth abyte conversion: failed due to inequality"); - - abyte fodder2[] = { -0x04, 0x00, 0x06, 0x00, 0x0a, 0x02, 0x03, 0x00, 0x06, 0x00, 0x48, 0x01, 0x1c, 0x00, 0x2c, 0x00, 0x04, 0x00, 0x09, 0x00, 0x17, 0x00, 0xff, 0xff, 0x00, 0x00, -0x00, 0x00, 0x09, 0x00 }; - fred = byte_array(sizeof(fodder2), fodder2); - astring as_text5("040006000a020300060048011c002c00040009001700ffff000000000900"); - byte_array harder_convert3; - byte_formatter::string_to_bytes(as_text5, harder_convert3); -astring back5; -byte_formatter::bytes_to_string(harder_convert3, back5); -LOG(astring("got fifth: ") + back5); - ASSERT_EQUAL(fred, harder_convert3, "fifth abyte conversion: failed due to inequality"); - -#ifndef EMBEDDED_BUILD - // now a test of parse_dump. - astring fred_dump; - byte_formatter::text_dump(fred_dump, fred, 0x993834); -LOG("fred dump..."); -LOG(fred_dump); - byte_array fred_like; - byte_formatter::parse_dump(fred_dump, fred_like); - ASSERT_EQUAL(fred, fred_like, "parse_dump test: failed due to inequality"); -#endif - } - { - // an endian tester. - basis::un_short test1 = 0x3c5f; - LOG("0x3c5f in intel:"); - LOG(byte_formatter::text_dump((abyte *)&test1, 2)); - basis::un_int test2 = 0x9eaad0cb; - LOG("0x9eaad0cb in intel:"); - LOG(byte_formatter::text_dump((abyte *)&test2, 4)); - - // now see what they look like as packables. - byte_array testa; - structures::attach(testa, test1); - LOG("0x3c5f in package:"); - LOG(byte_formatter::text_dump(testa)); - byte_array testb; - structures::attach(testb, test2); - LOG("0x9eaad0cb in package:"); - LOG(byte_formatter::text_dump(testb)); - } - - return final_report(); -} - -HOOPLE_MAIN(test_byte_format, ); - diff --git a/core/library/tests_textual/test_parse_csv.cpp b/core/library/tests_textual/test_parse_csv.cpp deleted file mode 100644 index 99c7b5b8..00000000 --- a/core/library/tests_textual/test_parse_csv.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* -* Name : test parsing of csv -* Author : Chris Koeritz -* Purpose: Checks that the CSV parsing function handles a few common scenarios. -** -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) - -// the number of times we scan our data file for performance test. -const int MAX_DATA_FILE_ITERS = 4000; - -class test_parsing_csv : public virtual unit_base, public virtual application_shell -{ -public: - test_parsing_csv() {} - DEFINE_CLASS_NAME("test_parsing_csv"); - int execute(); -}; - -//hmmm: too congratulatory? -#define COMPLAIN_FIELD(list, index, value) \ - ASSERT_EQUAL(list[index], astring(value), \ - a_sprintf("comparison test should have field %d correct in %s", index, #list)) - -int test_parsing_csv::execute() -{ - FUNCDEF("execute"); - astring line1 = "\"fupe\",\"snoorp\",\"lutem\",\"fipe\""; - string_array fields1; - bool works1 = list_parsing::parse_csv_line(line1, fields1); - ASSERT_TRUE(works1, "first test should not fail to parse"); -//LOG(a_sprintf("fields len now %d", fields1.length())); - ASSERT_EQUAL(fields1.length(), 4, "first test should have right count of strings found"); - COMPLAIN_FIELD(fields1, 0, "fupe"); - COMPLAIN_FIELD(fields1, 1, "snoorp"); - COMPLAIN_FIELD(fields1, 2, "lutem"); - COMPLAIN_FIELD(fields1, 3, "fipe"); - - astring line2 = "fupe,\"snoorp\",lutem,\"fipe\""; - string_array fields2; - bool works2 = list_parsing::parse_csv_line(line2, fields2); - ASSERT_TRUE(works2, "second test should not fail to parse"); - ASSERT_EQUAL(fields2.length(), 4, "second test should have right count of strings found"); - COMPLAIN_FIELD(fields2, 0, "fupe"); - COMPLAIN_FIELD(fields2, 1, "snoorp"); - COMPLAIN_FIELD(fields2, 2, "lutem"); - COMPLAIN_FIELD(fields2, 3, "fipe"); - - astring line3 = "\"lowenburger\",\"wazizzle\",morphel"; - string_array fields3; - bool works3 = list_parsing::parse_csv_line(line3, fields3); - ASSERT_TRUE(works3, "third test should not fail to parse"); - ASSERT_EQUAL(fields3.length(), 3, "third test should have right count of strings found"); - COMPLAIN_FIELD(fields3, 0, "lowenburger"); - COMPLAIN_FIELD(fields3, 1, "wazizzle"); - COMPLAIN_FIELD(fields3, 2, "morphel"); - - astring line4 = "\"lowenburger\",\"wazizzle\",morphel,"; - string_array fields4; - bool works4 = list_parsing::parse_csv_line(line4, fields4); - ASSERT_TRUE(works4, "fourth test should not fail to parse"); - ASSERT_EQUAL(fields4.length(), 4, "fourth test should not have wrong count of strings found"); - COMPLAIN_FIELD(fields4, 0, "lowenburger"); - COMPLAIN_FIELD(fields4, 1, "wazizzle"); - COMPLAIN_FIELD(fields4, 2, "morphel"); - COMPLAIN_FIELD(fields4, 3, ""); - - astring line5 = "\"lowenburger\",,"; - string_array fields5; - bool works5 = list_parsing::parse_csv_line(line5, fields5); - ASSERT_TRUE(works5, "fifth test should not fail to parse"); - ASSERT_EQUAL(fields5.length(), 3, "fifth test should have right count of strings found"); - COMPLAIN_FIELD(fields5, 0, "lowenburger"); - COMPLAIN_FIELD(fields5, 1, ""); - COMPLAIN_FIELD(fields5, 2, ""); - - astring line6 = ",,,\"rasputy\",,\"spunk\",ralph"; - string_array fields6; - bool works6 = list_parsing::parse_csv_line(line6, fields6); - ASSERT_TRUE(works6, "sixth test should not fail to parse"); - ASSERT_EQUAL(fields6.length(), 7, "sixth test should have right count of strings found"); - COMPLAIN_FIELD(fields6, 0, ""); - COMPLAIN_FIELD(fields6, 1, ""); - COMPLAIN_FIELD(fields6, 2, ""); - COMPLAIN_FIELD(fields6, 3, "rasputy"); - COMPLAIN_FIELD(fields6, 4, ""); - COMPLAIN_FIELD(fields6, 5, "spunk"); - COMPLAIN_FIELD(fields6, 6, "ralph"); - - astring line7 = "\"SRV0001337CHN0000001DSP0000001SRV0001337LAY0003108,16,0,8,192\",\"\\\"row_3\\\" on 12.5.55.159\",3"; - string_array fields7; - bool works7 = list_parsing::parse_csv_line(line7, fields7); - ASSERT_TRUE(works7, "seventh test should not fail to parse"); - ASSERT_EQUAL(fields7.length(), 3, "seventh test should have right count of strings found"); - COMPLAIN_FIELD(fields7, 0, "SRV0001337CHN0000001DSP0000001SRV0001337LAY0003108,16,0,8,192"); - COMPLAIN_FIELD(fields7, 1, "\"row_3\" on 12.5.55.159"); - COMPLAIN_FIELD(fields7, 2, "3"); - - // test 8... use data file. - filename df_dir = filename(application_configuration::application_name()).dirname(); - byte_filer test_data(df_dir.raw() + "/df_1.csv", "rb"); - string_array parsed; - string_array lines; - astring curr_line; - int read_result; - while ( (read_result = test_data.getline(curr_line, 1024)) > 0 ) - lines += curr_line; - if (lines.length()) { - // now we have the data file loaded. - stopwatch clicker; - clicker.start(); - for (int iterations = 0; iterations < MAX_DATA_FILE_ITERS; iterations++) { - for (int line = 0; line < lines.length(); line++) { - const astring ¤t = lines[line]; - list_parsing::parse_csv_line(current, parsed); - } - } - clicker.stop(); - log(a_sprintf("%d csv lines with %d iterations took %d ms (or %d s).", - lines.length(), MAX_DATA_FILE_ITERS, clicker.elapsed(), - clicker.elapsed() / 1000)); - } - - // test 9: process results of create_csv_line. - string_array fields9; - fields9 += "ACk\"boozort"; - fields9 += "sme\"ra\"\"foop"; - fields9 += "\"gumby\""; - astring line9 = "\"ACk\\\"boozort\",\"sme\\\"ra\\\"\\\"foop\",\"\\\"gumby\\\"\""; - astring gen_line_9; - list_parsing::create_csv_line(fields9, gen_line_9); -//log(astring(" got gen line: ") + gen_line_9); -//log(astring("expected line: ") + line9); - ASSERT_EQUAL(gen_line_9, line9, "ninth test should not fail to create expected text"); - string_array fields9_b; - bool works9 = list_parsing::parse_csv_line(gen_line_9, fields9_b); - ASSERT_TRUE(works9, "ninth test should not fail to parse"); - ASSERT_TRUE(fields9_b == fields9, "ninth test should match original fields"); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_parsing_csv, ) - diff --git a/core/library/tests_textual/test_splitter.cpp b/core/library/tests_textual/test_splitter.cpp deleted file mode 100644 index be07b62b..00000000 --- a/core/library/tests_textual/test_splitter.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* -* Name : test_splitter -* Author : Chris Koeritz -* Purpose: Checks out the line splitting support to make sure it is working. -** -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) - -class test_splitter : public virtual unit_base, virtual public application_shell -{ -public: - test_splitter() {} - DEFINE_CLASS_NAME("test_splitter"); - int execute(); -}; - -astring show_limits(int min_col, int max_col) -{ - astring to_return; - for (int i = 0; i <= max_col; i++) { - if (i < min_col) to_return += " "; - else to_return += "+"; - } - return to_return; -} - -#define SHOW_SPLIT(str, low, high) \ - string_manipulation::split_lines(str, output, low, high); \ - LOG(""); formal(temp)\ - LOG(show_limits(low, high)); \ - LOG(output + "<<<<"); \ - LOG(show_limits(low, high)) - -int test_splitter::execute() -{ - FUNCDEF("execute"); - - astring split_1 = "This is a fairly long paragraph that will be split a few different ways to check the splitting logic. It will be interesting to see how things like spaces, and punctuation, are handled."; - - astring output; - - SHOW_SPLIT(split_1, 0, 1); - SHOW_SPLIT(split_1, 0, 10); - SHOW_SPLIT(split_1, 10, 30); - SHOW_SPLIT(split_1, 20, 35); - SHOW_SPLIT(split_1, 4, 50); - SHOW_SPLIT(split_1, 8, 12); - - astring split_2 = "Here's another string.\r\nThere are embedded carriage returns after every sentence.\r\nSo, this should be a good test of how those things are handled when they're seen in the body of the text.\r\nThe next one is, hey, guess what?\r\nIt's a simple LF instead of CRLF; here it comes:\nHow did that look compared the others?\nThe previous was another bare one.\r\nWhen can I stop writing this stupid paragraph?\r\nSoon hopefully."; - - SHOW_SPLIT(split_2, 5, 20); - SHOW_SPLIT(split_2, 0, 30); - SHOW_SPLIT(split_2, 8, 11); - SHOW_SPLIT(split_2, 28, 41); - SHOW_SPLIT(split_2, 58, 79); - - astring split_3 = "This string exists for just one purpose; it will be showing how the thing handles a really long word at the end. And that word is... califragilisticexpialadociuosberriesinatreearerottingnowsomamacasscanyoupleasehelpme"; - - SHOW_SPLIT(split_3, 0, 5); - SHOW_SPLIT(split_3, 10, 30); - - astring split_4 = "This string\n\n\nhas multiple CRs gwan on.\r\n\r\nDoes this cause problems?\n\n\n\n"; - - SHOW_SPLIT(split_4, 3, 10); - SHOW_SPLIT(split_4, 8, 20); - - return final_report(); -} - -HOOPLE_MAIN(test_splitter, ); - diff --git a/core/library/tests_textual/test_xml_generator.cpp b/core/library/tests_textual/test_xml_generator.cpp deleted file mode 100644 index 923e438c..00000000 --- a/core/library/tests_textual/test_xml_generator.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* -* Name : test_xml_generator -* Author : Chris Koeritz -* Purpose: Checks out whether the XML writer seems to be functional. -** -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -//using namespace filesystem; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) - -class test_xml_generator : public virtual unit_base, virtual public application_shell -{ -public: - test_xml_generator() {} - DEFINE_CLASS_NAME("test_xml_generator"); - int execute(); -}; - -#define OPERATE_XML(func, args, test_name) { \ - outcome ret = ted.func args; \ - ASSERT_EQUAL(ret.value(), xml_generator::OKAY, \ - astring(test_name) + astring(": failed to ") + #func); \ -} - -int test_xml_generator::execute() -{ - FUNCDEF("execute"); - xml_generator ted; - #define TEST "boilerplate" - - string_table attribs; - attribs.add("bluebird", "petunia chowder"); - OPERATE_XML(add_header, ("glommage", attribs), TEST); - - OPERATE_XML(open_tag, ("Recipe"), TEST); - - OPERATE_XML(open_tag, ("Name"), TEST); - OPERATE_XML(add_content, ("Lime Jello Marshmallow Cottage Cheese Surprise"), - TEST); - OPERATE_XML(close_tag, ("Name"), TEST); - - OPERATE_XML(open_tag, ("Description"), TEST); - OPERATE_XML(add_content, ("My grandma's favorite (may she rest in peace)."), - TEST); - OPERATE_XML(close_tag, ("Description"), TEST); - - #undef TEST - #define TEST "stirring ingredients" - OPERATE_XML(open_tag, ("Ingredients"), TEST); - - ////////////// - - OPERATE_XML(open_tag, ("Ingredient"), TEST); - - attribs.reset(); - attribs.add("unit", "box"); - OPERATE_XML(open_tag, ("Qty", attribs), TEST); - OPERATE_XML(add_content, ("1"), TEST); - OPERATE_XML(close_tag, ("Qty"), TEST); - - OPERATE_XML(open_tag, ("Item"), TEST); - OPERATE_XML(add_content, ("lime gelatin"), TEST); - OPERATE_XML(close_tag, ("Item"), TEST); - - OPERATE_XML(close_tag, ("Ingredient"), TEST); - - ////////////// - - OPERATE_XML(open_tag, ("Ingredient"), TEST); - - attribs.reset(); - attribs.add("unit", "g"); - OPERATE_XML(open_tag, ("Qty", attribs), TEST); - OPERATE_XML(add_content, ("500"), TEST); - OPERATE_XML(close_tag, ("Qty"), TEST); - - OPERATE_XML(open_tag, ("Item"), TEST); - OPERATE_XML(add_content, ("multicolored tiny marshmallows"), TEST); - OPERATE_XML(close_tag, ("Item"), TEST); - - OPERATE_XML(close_tag, ("Ingredient"), TEST); - - ////////////// - - #undef TEST - #define TEST "closing the bowl" - - OPERATE_XML(close_tag, ("Ingredients"), TEST); - - astring generated = ted.generate(); - LOG(astring("XML generated is as follows:")); - LOG(generated); - - return final_report(); -} - -HOOPLE_MAIN(test_xml_generator, ) - diff --git a/core/library/tests_timely/makefile b/core/library/tests_timely/makefile deleted file mode 100644 index 3b91fe7b..00000000 --- a/core/library/tests_timely/makefile +++ /dev/null @@ -1,12 +0,0 @@ -include cpp/variables.def - -PROJECT = tests_timely -TYPE = test -TARGETS = test_earth_time.exe -DEFINITIONS += USE_HOOPLE_DLLS -LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ - structures textual timely filesystem structures basis -RUN_TARGETS = $(ACTUAL_TARGETS) - -include cpp/rules.def - diff --git a/core/library/tests_timely/test_earth_time.cpp b/core/library/tests_timely/test_earth_time.cpp deleted file mode 100644 index 716a14b1..00000000 --- a/core/library/tests_timely/test_earth_time.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* -* Name : test_earth_time * -* Author : Chris Koeritz * -** -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#define DEBUG_EARTH_TIME - // set this to enable debugging features of the string class. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace mathematics; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; -using namespace unit_test; - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) -#undef BASE_LOG -#define BASE_LOG(s) STAMPED_EMERGENCY_LOG(program_wide_logger::get(), s) - -const int TIME_FORMAT = clock_time::MERIDIAN | clock_time::SECONDS - | clock_time::MILLISECONDS; - // the way we like to see our seconds get printed out. - -////////////// - -class test_earth_time : virtual public unit_base, virtual public application_shell -{ -public: - test_earth_time() {} - DEFINE_CLASS_NAME("test_earth_time"); - - virtual int execute(); - - void run_test_01(); - void run_test_02(); -}; - -////////////// - -void test_earth_time::run_test_01() -{ - FUNCDEF("run_test_01"); - // this test makes sure that clock_time's normalize is working as expected. - - time_locus checker_1(clock_time(12, 0, 60), day_in_year(), 2007); - clock_time::normalize(checker_1); - time_locus compare_1(clock_time(12, 1, 0), day_in_year(), 2007); -//BASE_LOG(astring("a=") + checker_1.text_form(TIME_FORMAT)); -//BASE_LOG(astring("b=") + compare_1.text_form(TIME_FORMAT)); - ASSERT_EQUAL(checker_1, compare_1, "normalize should not fail test 1"); - - time_locus checker_2(clock_time(12, 0, -1), day_in_year(), 2007); - clock_time::normalize(checker_2); - time_locus compare_2(clock_time(11, 59, 59), day_in_year(), 2007); - ASSERT_EQUAL(checker_2, compare_2, "normalize should not fail test 2"); - - time_locus checker_3(clock_time(11, 59, 61), day_in_year(), 2007); - clock_time::normalize(checker_3); - time_locus compare_3(clock_time(12, 00, 01), day_in_year(), 2007); - ASSERT_EQUAL(checker_3, compare_3, "normalize should not fail test 3"); - - time_locus checker_4(clock_time(12, 54, -61), day_in_year(), 2007); - clock_time::normalize(checker_4); - time_locus compare_4(clock_time(12, 52, 59), day_in_year(), 2007); - ASSERT_EQUAL(checker_4, compare_4, "normalize should not fail test 4"); - - time_locus checker_5(clock_time(12, -32, -62), day_in_year(), 2007); - clock_time::normalize(checker_5); - time_locus compare_5(clock_time(11, 26, 58), day_in_year(), 2007); - ASSERT_EQUAL(checker_5, compare_5, "normalize should not fail test 5"); -} - -void test_earth_time::run_test_02() -{ - FUNCDEF("run_test_02"); - // this test makes sure that day_in_year's normalize is working as expected. - - time_locus checker_1(clock_time(0, 0, -1), day_in_year(JANUARY, 1), 2007); - time_locus::normalize(checker_1); - time_locus compare_1(clock_time(23, 59, 59), day_in_year(DECEMBER, 31), 2006); -//BASE_LOG(astring("a=") + checker_1.text_form(TIME_FORMAT)); -//BASE_LOG(astring("b=") + compare_1.text_form(TIME_FORMAT)); - ASSERT_EQUAL(checker_1, compare_1, "normalize should not fail test 1"); - - time_locus checker_2(clock_time(23, 59, 60), day_in_year(DECEMBER, 31), 2007); - time_locus::normalize(checker_2); - time_locus compare_2(clock_time(0, 0, 0), day_in_year(JANUARY, 1), 2008); - ASSERT_EQUAL(checker_2, compare_2, "normalize should not fail test 2"); - - -//add more cases! -// test leap years -// test lotso things. - -} - -int test_earth_time::execute() -{ - FUNCDEF("execute"); - - run_test_01(); - run_test_02(); - - return final_report(); -} - -////////////// - -HOOPLE_MAIN(test_earth_time, ) - diff --git a/core/library/textual/byte_formatter.cpp b/core/library/textual/byte_formatter.cpp deleted file mode 100644 index 0aa73932..00000000 --- a/core/library/textual/byte_formatter.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/*****************************************************************************\ -* * -* Name : byte_formatter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "byte_formatter.h" -#include "parser_bits.h" -#include "string_manipulation.h" - -#include -#include -#include - -//#define DEBUG_BYTE_FORMAT - // uncomment for noisier version. - -#undef LOG -#ifdef DEBUG_BYTE_FORMAT - #define LOG(s) printf("%s\n", astring(s).s()) -#else - #define LOG(s) {} -#endif - -#define LINE_SIZE 80 - -using namespace basis; -using namespace structures; - -namespace textual { - -void byte_formatter::print_char(abyte to_print, astring &out, char replace) -{ - int temp = to_print % 128; - if (!parser_bits::is_printable_ascii(to_print)) out += replace; - else out += char(temp); -} - -void byte_formatter::print_chars(const abyte *to_print, int len, astring &out, char replace) -{ - for (int i = 0; i < len; i++) - print_char(to_print[i], out, replace); -} - -void byte_formatter::make_eight(basis::un_int num, astring &out) -{ - basis::un_int thresh = 0x10000000; - while (thresh >= 0x10) { - if (num < thresh) - out += '0'; - thresh >>= 4; // zap a nibble. - } -} - -astring byte_formatter::text_dump(const abyte *location, basis::un_int length, basis::un_int label, - const char *eol) -{ - astring to_return; - text_dump(to_return, location, length, label, eol); - return to_return; -} - -void byte_formatter::text_dump(astring &output, const byte_array &to_dump, basis::un_int label, - const char *eol) -{ - text_dump(output, to_dump.observe(), to_dump.length(), label, eol); -} - -astring byte_formatter::text_dump(const byte_array &to_dump, basis::un_int label, const char *eol) -{ - astring output; - text_dump(output, to_dump.observe(), to_dump.length(), label, eol); - return output; -} - -// this is the real version of text_dump. all the others use it. -void byte_formatter::text_dump(astring &to_return, const abyte *location, basis::un_int length, - basis::un_int label, const char *eol) -{ - to_return = ""; - int entry_size = 4; - int preamble = 14; - - basis::un_int entries_per_line = (LINE_SIZE - preamble) / entry_size; - - for (basis::un_int i = 0; i < length; i += entries_per_line) { - make_eight(i + label, to_return); - to_return += astring(astring::SPRINTF, "%x", i + label) + astring(" | "); - for (basis::un_int j = 0; j < entries_per_line; j++) { - if (i + j >= length) { - // if at the end of the loop, just print spaces. - to_return += " "; - } else { - int ord_of_current_char = *(location + i + j) & 0xFF; - if (ord_of_current_char < 0x10) to_return += '0'; - to_return += astring(astring::SPRINTF, "%x", int(ord_of_current_char)); - to_return += ' '; - } - } - - to_return += "| "; - for (basis::un_int k = i; k < i + entries_per_line; k++) { - if (k >= length) to_return += ' '; - // if past the end of the block, just add spaces. - else print_char(*(location + k), to_return); - } - to_return += astring(" |") + eol; - } -} - -void byte_formatter::parse_dump(const astring &dumped_form, byte_array &bytes_found) -{ - bytes_found.reset(); - string_array lines_found; - // iterate over the string and break it up into lines. - for (int i = 0; i < dumped_form.length(); i++) { - int indy = dumped_form.find('\n', i); -//hmmm: not platform invariant. what about '\r' if we see it? - - if (negative(indy)) { - // no more lines found. - if (i < dumped_form.length() - 1) { - // grab the last bit as a line. - lines_found += dumped_form.substring(i, dumped_form.length() - 1); - } - break; - } - // found a normal line ending, so drop everything from the current - // position up to the ending into the list of strings. - lines_found += dumped_form.substring(i, indy - 1); - i = indy + 1; // jump to next potential line. - } - // now process the lines that we've found. - for (int j = 0; j < lines_found.length(); j++) { - // first step is to find the pipe character that brackets the actual - // data. we ignore the "address" located before the pipe. - astring &s = lines_found[j]; - int bar_one = s.find('|', 0); - if (negative(bar_one)) continue; // skip this one; it's malformed. - // now we look for the second pipe that comes before the text form of - // the data. we don't care about the text or anything after. - int bar_two = s.find('|', bar_one + 1); - if (negative(bar_two)) continue; // skip for same reason. - astring s2 = s.substring(bar_one + 1, bar_two - 1); - byte_array this_part; - string_to_bytes(s2, this_part); - bytes_found += this_part; - } -} - -////////////// - -void byte_formatter::bytes_to_string(const abyte *to_convert, int length, astring &as_string, - bool space_delimited) -{ - if (!to_convert || !length) return; // nothing to do. - if (negative(length)) return; // bunk. - as_string = ""; // reset the output parameter. - - // the pattern is used for printing the bytes and considering the delimiter. - astring pattern("%02x"); - if (space_delimited) pattern += " "; - - // now zip through the array and dump it into the string. - for (int i = 0; i < length; i++) - as_string += astring(astring::SPRINTF, pattern.s(), to_convert[i]); -} - -// returns true if the character is within the valid ranges of hexadecimal -// nibbles (as text). -bool byte_formatter::in_hex_range(char to_check) -//hmmm: move this to parser bits. -{ - return ( (to_check <= '9') && (to_check >= '0') ) - || ( (to_check <= 'f') && (to_check >= 'a') ) - || ( (to_check <= 'F') && (to_check >= 'A') ); -} - -void byte_formatter::string_to_bytes(const char *to_convert, byte_array &as_array) -{ - as_array.reset(); // clear the array. - const int len = int(strlen(to_convert)); - - // the parser uses a simple state machine for processing the string. - enum states { FINDING_HEX, IGNORING_JUNK }; - states state = IGNORING_JUNK; - - int digits = 0; // the number of digits we've currently found. - int accumulator = 0; // the current hex duo. - - // loop through the string. - for (int i = 0; i < len; i++) { - switch (state) { - case IGNORING_JUNK: { - if (in_hex_range(to_convert[i])) { - i--; // skip back to where we were before now. - state = FINDING_HEX; - continue; // jump to the other state. - } - // otherwise, we could care less what the character is. - break; - } - case FINDING_HEX: { - if (digits >= 2) { - // we have finished a hex byte. - as_array += abyte(accumulator); - accumulator = 0; - digits = 0; - i--; // skip back for the byte we haven't eaten yet. - state = IGNORING_JUNK; // jump to other state for a new item. - continue; - } - // we really think this is a digit here and we're not through with - // accumulating them. - accumulator <<= 4; - digits++; - accumulator += string_manipulation::char_to_hex(to_convert[i]); - - // now we sneakily check the next character. - if (!in_hex_range(to_convert[i+1])) { - // we now know we should not be in this state for long. - if (digits) { - // there's still some undigested stuff. - digits = 2; // fake a finished byte. - continue; // keep going, but eat the character we were at. - } - // well, there's nothing lost if we just jump to that state. - state = IGNORING_JUNK; - continue; - } - break; - } - } - } - if (digits) { - // snag the last unfinished bit. - as_array += abyte(accumulator); - } -} - -void byte_formatter::bytes_to_string(const byte_array &to_convert, astring &as_string, - bool space_delimited) -{ - bytes_to_string(to_convert.observe(), to_convert.length(), as_string, - space_delimited); -} - -void byte_formatter::string_to_bytes(const astring &to_convert, byte_array &as_array) -{ string_to_bytes(to_convert.s(), as_array); } - -void byte_formatter::bytes_to_shifted_string(const byte_array &to_convert, astring &as_string) -{ -#ifdef DEBUG_BYTE_FORMAT - FUNCDEF("bytes_to_shifted_string"); -#endif - bit_vector splitter(8 * to_convert.length(), to_convert.observe()); - int i; // track our current position. - for (i = 0; i < splitter.bits(); i += 7) { - abyte curr = 1; // start with a bit set already. - for (int j = i; j < i + 7; j++) { - curr <<= 1; // move to the left. - if (j < splitter.bits()) - curr |= abyte(splitter.on(j)); // or in the current position. - } - as_string += char(curr); - } -#ifdef DEBUG_BYTE_FORMAT - LOG(a_sprintf("%d bytes comes out as %d char string.", - to_convert.length(), as_string.length()).s()); -#endif -} - -void byte_formatter::shifted_string_to_bytes(const astring &to_convert, byte_array &as_array) -{ -#ifdef DEBUG_BYTE_FORMAT - FUNCDEF("shifted_string_to_bytes"); -#endif - bit_vector accumulator; - - for (int i = 0; i < to_convert.length(); i++) { - abyte current = abyte(to_convert[i]) & 0x7F; - // get the current bits but remove the faux sign bit. - accumulator.resize(accumulator.bits() + 7); - // now shift off the individual pieces. - for (int j = 0; j < 7; j++) { - // get current bit's value. - current <<= 1; // shift it up. - abyte set_here = current & 0x80; // test the highest order bit. - // now flip that bit on or off based on what we saw. - accumulator.set_bit(i * 7 + j, bool(set_here)); - } - } - - int remainder = accumulator.bits() % 8; - accumulator.resize(accumulator.bits() - remainder); - // chop off any extraneous bits that are due to our shifting. - -#ifdef DEBUG_BYTE_FORMAT - // there should be no remainder. and the number of bits should be a multiple - // of eight now. - if (accumulator.bits() % 8) - deadly_error("byte_formatter", func, "number of bits is erroneous."); -#endif - - const byte_array &accumref = accumulator; - for (int q = 0; q < accumulator.bits() / 8; q++) - as_array += accumref[q]; - -#ifdef DEBUG_BYTE_FORMAT - LOG(a_sprintf("%d chars comes out as %d bytes.", - to_convert.length(), as_array.length()).s()); -#endif -} - -} // namespace - diff --git a/core/library/textual/byte_formatter.h b/core/library/textual/byte_formatter.h deleted file mode 100644 index b67f997e..00000000 --- a/core/library/textual/byte_formatter.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef BYTE_FORMATTER_CLASS -#define BYTE_FORMATTER_CLASS - -/*****************************************************************************\ -* * -* Name : byte_formatter * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1992-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -namespace textual { - -//! Provides functions for manipulating arrays of bytes. - -class byte_formatter : public virtual basis::root_object -{ -public: - virtual ~byte_formatter() {} - - DEFINE_CLASS_NAME("byte_formatter"); - - static void print_char(basis::abyte to_print, basis::astring &out, - char replace = '_'); - //!< prints the byte "to_print" into "out" as long as "to_print" is readable. - /*!< if it's not readable, then the "replace" is printed. */ - - static void print_chars(const basis::abyte *to_print, int length, - basis::astring &out, char replace = '_'); - //!< sends the bytes in "to_print" of "length" bytes into the string "out". - - static void text_dump(basis::astring &output, const basis::abyte *location, - basis::un_int length, basis::un_int label = 0, const char *eol = "\n"); - //!< prints out a block of memory in a human readable form. - /*!< it is stored in the "output" string. the "location" is where to dump, - the "length" is the number of bytes to dump, and the "label" is where to - start numbering the location label on the first line. the "eol" supplies - the line ending sequence to be used for the output file. this should be - "\r\n" for win32. */ - - static basis::astring text_dump(const basis::abyte *location, basis::un_int length, - basis::un_int label = 0, const char *eol = "\n"); - //!< this is a less efficient version of text_dump that returns a string. - /*!< it's easier to use when combining astrings. */ - - static void text_dump(basis::astring &output, - const basis::byte_array &to_dump, basis::un_int label = 0, const char *eol = "\n"); - //!< a version that operates on a byte_array and stores into a string. - static basis::astring text_dump(const basis::byte_array &to_dump, - basis::un_int label = 0, const char *eol = "\n"); - //!< a version that operates on a byte_array and returns a string. - - //! this operation performs the inverse of a text_dump. - static void parse_dump(const basis::astring &dumped_form, - basis::byte_array &bytes_found); - -////////////// - - static void bytes_to_string(const basis::byte_array &to_convert, - basis::astring &as_string, bool space_delimited = true); - //!< converts a byte_array into a string. - /*!< takes an array of bytes "to_convert" and spits out the equivalent form - "as_string". if "space_delimited" is true, then the bytes are separated - by spaces. */ - - static void string_to_bytes(const basis::astring &to_convert, - basis::byte_array &as_array); - //!< wrangles the string "to_convert" into an equivalent byte form "as_array". - /*!< this is a fairly forgiving conversion; it will accept any string and - strip out the hexadecimal bytes. spacing is optional, but every two - hex nibbles together will be taken as a byte. if there are an odd number - of nibbles, then the odd one will be taken as the least significant half - of a byte. */ - - static void bytes_to_string(const basis::abyte *to_convert, int length, - basis::astring &as_string, bool space_delimited = true); - //!< a version that accepts a byte pointer and length, rather than byte_array. - - static void string_to_bytes(const char *to_convert, - basis::byte_array &as_array); - //!< a version that works with the char pointer rather than an astring. - -////////////// - - static void bytes_to_shifted_string - (const basis::byte_array &to_convert, basis::astring &as_string); - //!< this is a special purpose converter from bytes to character strings. - /*!< it merely ensures that the "as_string" version has no zero bytes - besides the end of string null byte. this packs 7 bits of data into each - character, resulting in an 87.5% efficient string packing of the array. - the resulting string is not readable. the "as_string" parameter is not - reset; any data will be appended to it. */ - - static void shifted_string_to_bytes(const basis::astring &to_convert, - basis::byte_array &as_array); - //!< unshifts a string "to_convert" back into a byte_array. - /*!< converts a string "to_convert" created by bytes_to_shifted_string() into - the original array of bytes and stores it in "as_array". the "as_array" - parameter is not reset; any data will be appended to it. */ - -////////////// - - // utility methods to help building the formatted strings. - - static void make_eight(basis::un_int num, basis::astring &out); - - static bool in_hex_range(char to_check); -}; - -} //namespace. - -#endif - diff --git a/core/library/textual/list_parsing.cpp b/core/library/textual/list_parsing.cpp deleted file mode 100644 index 9cac4a8f..00000000 --- a/core/library/textual/list_parsing.cpp +++ /dev/null @@ -1,279 +0,0 @@ -/*****************************************************************************\ -* * -* Name : list_parsing * -* Author : Chris Koeritz * -* Author : Gary Hardley * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "list_parsing.h" -#include "parser_bits.h" - -#include -#include -#include - -#include -#include - -using namespace basis; -using namespace structures; - -namespace textual { - -#undef LOG -#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) - -list_parsing::~list_parsing() {} // needed since we use the class_name macro. - -// by Gary Hardley. -bool list_parsing::get_ids_from_string(const astring &to_parse, int_set &identifiers) -{ - identifiers.clear(); // clear existing ids, if any. - int_array found; - bool ret = get_ids_from_string(to_parse, found); - if (!ret) return false; - for (int i = 0; i < found.length(); i++) identifiers.add(found[i]); - return true; -} - -// by Gary Hardley. -bool list_parsing::get_ids_from_string(const astring &to_parse, - int_array &identifiers) -{ - identifiers.reset(); // clear existing ids, if any. - if (!to_parse) return false; - // if an empty string is passed, return an empty set. - - int last_id = -1; - int tmp_id; - bool done = false; - char last_separator = ' '; - - int index = 0; - while (!done && (index < to_parse.length())) { - tmp_id = 0; - bool got_digit = false; - while ( (to_parse[index] != ',') && (to_parse[index] != '-') - && (to_parse[index] != ' ') && (index < to_parse.length()) ) { - if (!isdigit(to_parse[index])) return false; - tmp_id *= 10; - tmp_id += int(to_parse[index++]) - 0x30; - got_digit = true; - } - - if (got_digit) { - if (tmp_id > MAXINT32) return false; - - if (last_id == -1) { - last_id = tmp_id; - identifiers += last_id; - } else { - // if the last separator was a dash, this is a range - if (last_separator == '-') { - if (tmp_id >= last_id) { - for (int i = last_id + 1; i <= tmp_id; i++) - identifiers += i; - } - else { - for (int i = tmp_id; i < last_id; i++) - identifiers += i; - } - last_id = 0; - last_separator = ' '; - } else { - last_id = tmp_id; - identifiers += last_id; - } - } - } else { - // did not read an address, to_parse[index] must be a non-digit. - if ( (to_parse[index] != ' ') && (to_parse[index] != '-') - && (to_parse[index] != ',') ) return false; - last_separator = to_parse[index++]; - } - } - return true; -} - -//by chris koeritz. -astring list_parsing::put_ids_in_string(const int_set &ids, char separator) -{ - astring to_return; - for (int i = 0; i < ids.length(); i++) { - to_return += a_sprintf("%d", ids[i]); - if (i < ids.length() - 1) { - to_return += separator; - to_return += " "; - } - } - return to_return; -} - -//by chris koeritz. -astring list_parsing::put_ids_in_string(const int_array &ids, char separator) -{ - astring to_return; - for (int i = 0; i < ids.length(); i++) { - to_return += a_sprintf("%d", ids[i]); - if (i < ids.length() - 1) { - to_return += separator; - to_return += " "; - } - } - return to_return; -} - -// ensures that quotes inside the string "to_emit" are escaped. -astring list_parsing::emit_quoted_chunk(const astring &to_emit) -{ - astring to_return('\0', 256); // avoid reallocations with large pre-alloc. - to_return = ""; // reset to get blank string but keep pre-alloc. - for (int i = 0; i < to_emit.length(); i++) { - char next_char = to_emit[i]; - if ( (next_char == '"') || (next_char == '\\') ) - to_return += "\\"; // add the escape before quote or backslash. - to_return += astring(next_char, 1); - } - return to_return; -} - -void list_parsing::create_csv_line(const string_table &to_csv, astring &target) -{ - target = astring::empty_string(); - for (int i = 0; i < to_csv.symbols(); i++) { - target += astring("\"") + emit_quoted_chunk(to_csv.name(i)) - + "=" + emit_quoted_chunk(to_csv[i]) + "\""; - if (i < to_csv.symbols() - 1) target += ","; - } -} - -void list_parsing::create_csv_line(const string_array &to_csv, astring &target) -{ - target = astring::empty_string(); - for (int i = 0; i < to_csv.length(); i++) { - target += astring("\"") + emit_quoted_chunk(to_csv[i]) + "\""; - if (i < to_csv.length() - 1) target += ","; - } -} - -// we do handle escaped quotes for csv parsing, so check for backslash. -// and since we handle escaped quotes, we also have to handle escaping the -// backslash (otherwise a quoted item with a backslash as the last character -// cannot be handled appropriately, because it will be interpreted as an -// escaped quote instead). no other escapes are implemented right now. -#define handle_escapes \ - if (to_parse[i] == '\\') { \ - if ( (to_parse[i + 1] == '"') || (to_parse[i + 1] == '\\') ) { \ - i++; \ - accumulator += to_parse[i]; \ - continue; /* skip normal handling in sequel. */ \ - } \ - } - -const int ARRAY_PREFILL_AMOUNT = 7; - // a random default for pre-filling. - -#define ADD_LINE_TO_FIELDS(new_line) { \ - storage_slot++; /* move to next place to store item. */ \ - /* make sure we have enough space for the next slot and then some. */ \ -/*LOG(a_sprintf("fields curr=%d stowslot=%d", fields.length(), storage_slot));*/ \ - if (fields.length() < storage_slot + 2) \ - fields.insert(fields.length(), ARRAY_PREFILL_AMOUNT); \ -/*LOG(a_sprintf("now fields=%d stowslot=%d", fields.length(), storage_slot));*/ \ - fields[storage_slot] = new_line; \ -} - -//hmmm: parameterize what is meant by a quote. maybe comma too. -//by chris koeritz. -bool list_parsing::parse_csv_line(const astring &to_parse, string_array &fields) -{ - FUNCDEF("parse_csv_line"); - // the current field we're chowing. we puff it out to start with to - // avoid paying for expanding its memory later. - astring accumulator(' ', 256); - accumulator = astring::empty_string(); - - // the state machine goes through these states until the entire string - // is consumed. - enum states { seeking_quote, eating_string, seeking_comma }; - states state = seeking_quote; - - bool no_second_quote = false; // true if we started without a quote. - bool just_saw_comma = false; // true if seeking comma was the last state. - - int storage_slot = -1; - - for (int i = 0; i < to_parse.length(); i++) { - switch (state) { - case seeking_quote: - if (parser_bits::white_space(to_parse[i])) continue; - if (to_parse[i] == ',') { - // a missing quoted string counts as an empty string. - ADD_LINE_TO_FIELDS(astring::empty_string()); - just_saw_comma = true; - continue; - } - just_saw_comma = false; // cancel that state. - if (to_parse[i] != '"') { - // short circuit the need for a quote. - accumulator += to_parse[i]; - no_second_quote = true; - } - state = eating_string; - break; - case eating_string: - just_saw_comma = false; // no longer true. - if (no_second_quote && (to_parse[i] != ',') ) { - handle_escapes; - accumulator += to_parse[i]; - } else if (!no_second_quote && (to_parse[i] != '"') ) { - handle_escapes; - accumulator += to_parse[i]; - } else { - // we found the closing quote (or comma). add the string. - if (no_second_quote) { - state = seeking_quote; - just_saw_comma = true; - } else state = seeking_comma; - ADD_LINE_TO_FIELDS(accumulator) - accumulator = astring::empty_string(); - no_second_quote = false; - } - break; - case seeking_comma: - if (parser_bits::white_space(to_parse[i])) continue; - if (to_parse[i] == ',') { - // we got what we wanted. - state = seeking_quote; - just_saw_comma = true; - continue; - } - // well, there was no comma. that's an error. - return false; - break; - default: { - LOG("erroneous state reached during csv parsing"); - break; - } - } - } - if ( (state == eating_string) && (accumulator.length()) ) - ADD_LINE_TO_FIELDS(accumulator) - else if (just_saw_comma) - ADD_LINE_TO_FIELDS(astring::empty_string()) - if (fields.length() > storage_slot + 1) - fields.zap(storage_slot + 1, fields.last()); - return true; -} - -} //namespace. - - diff --git a/core/library/textual/list_parsing.h b/core/library/textual/list_parsing.h deleted file mode 100644 index 0a2c9292..00000000 --- a/core/library/textual/list_parsing.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef LIST_PARSING_CLASS -#define LIST_PARSING_CLASS - -/*****************************************************************************\ -* * -* Name : list_parsing * -* Author : Chris Koeritz * -* Author : Gary Hardley * -* * -******************************************************************************* -* Copyright (c) 2002-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -namespace textual { - -//! A set of functions that help out with parsing lists of things. - -class list_parsing -{ -public: - virtual ~list_parsing(); - DEFINE_CLASS_NAME("list_parsing"); - - static bool get_ids_from_string(const basis::astring &string, structures::int_set &ids); - //!< returns true if a set of unique ids can be extracted from "string". - /*!< valid separators are spaces, commas, hyphens. note that this - returns an int_set although the integers will all be non-negative. - e.g. "1-4,5 6 7,30-25" is a valid string. */ - - static bool get_ids_from_string(const basis::astring &string, basis::int_array &ids); - //!< same as above except result is an array -- to preserve order. - /*!< this also retains duplicates. */ - - static basis::astring put_ids_in_string(const structures::int_set &ids, char separator = ','); - //!< returns a string containing a "separator" separated list of ids. - /*!< spaces are also used between entries for readability. */ - static basis::astring put_ids_in_string(const basis::int_array &ids, char separator = ','); - //!< operates on an array instead of a set. - - static basis::astring emit_quoted_chunk(const basis::astring &to_emit); - //!< ensures that quotes inside the string "to_emit" are escaped. - - static bool parse_csv_line(const basis::astring &to_parse, structures::string_array &fields); - //!< examines the string "to_parse" which should be in csv format. - /*!< the "fields" list is set to the entries found on the line. true is - returned if the line parsed without any errors. this method will accept - a backslash as an escape character if it is immediately followed by a - quote character or another backslash character. no other escapes are - currently supported; backslashes will be taken literally otherwise. */ - - static void create_csv_line(const structures::string_array &to_csv, basis::astring &target); - static void create_csv_line(const structures::string_table &to_csv, basis::astring &target); - //!< writes a CSV line of text into the "target" from the items in "to_csv". - /*!< the "target" is reset before the line is stored there; thus, this is - not cumulative. further, the end of line character is not appended. this - will escape quote and backslash characters with a prepended backslash. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/textual/makefile b/core/library/textual/makefile deleted file mode 100644 index 3b7d9199..00000000 --- a/core/library/textual/makefile +++ /dev/null @@ -1,10 +0,0 @@ -include cpp/variables.def - -PROJECT = textual -TYPE = library -SOURCE = byte_formatter.cpp list_parsing.cpp parser_bits.cpp string_manipulation.cpp \ - xml_generator.cpp xml_parser.cpp -TARGETS = textual.lib - -include cpp/rules.def - diff --git a/core/library/textual/parser_bits.cpp b/core/library/textual/parser_bits.cpp deleted file mode 100644 index 0bf33179..00000000 --- a/core/library/textual/parser_bits.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/*****************************************************************************\ -* * -* Name : parser_bits * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "parser_bits.h" - -#include -#include -#include - -#include -#include - -using namespace basis; - -#undef LOG -#define LOG(prf) printf("%s\n", basis::astring(prf).s()) - -namespace textual { - -parser_bits::line_ending parser_bits::platform_eol() -{ -#ifdef __UNIX__ - // obviously a unix OS, unless someone's playing games with us. - return LF_AT_END; -#elif defined(__WIN32__) - // smells like DOS. - return CRLF_AT_END; -#else - // pick the unix default if we can't tell. - return LF_AT_END; -#endif -} - -const char *parser_bits::eol_to_chars(line_ending end) -{ - static const char *CRLF_AT_END_STRING = "\r\n"; - static const char *LF_AT_END_STRING = "\n"; - static const char *NO_ENDING_STRING = ""; - - switch (end) { - case CRLF_AT_END: return CRLF_AT_END_STRING; - case NO_ENDING: return NO_ENDING_STRING; - case LF_AT_END: // fall-through to default. - default: return LF_AT_END_STRING; - } -} - -const char *parser_bits::platform_eol_to_chars() -{ return eol_to_chars(platform_eol()); } - -bool parser_bits::is_printable_ascii(char to_check) -{ return (to_check >= 32) && (to_check <= 126); } - -bool parser_bits::white_space_no_cr(char to_check) -{ return (to_check == ' ') || (to_check == '\t'); } - -bool parser_bits::is_eol(char to_check) -{ return (to_check == '\n') || (to_check == '\r'); } - -bool parser_bits::white_space(char to_check) -{ return white_space_no_cr(to_check) || is_eol(to_check); } - -void parser_bits::translate_CR_for_platform(astring &to_translate) -{ - line_ending plat_eol = platform_eol(); - bool last_was_lf = false; - for (int i = 0; i <= to_translate.end(); i++) { - if (to_translate[i] == '\r') { - if (last_was_lf) continue; // ignore two in a row. - last_was_lf = true; - } else if (to_translate[i] == '\n') { - if (last_was_lf) { - if (plat_eol != CRLF_AT_END) { - // fix it, since there was not supposed to be an LF. - to_translate.zap(i - 1, i - 1); - i--; - } - } else { - if (plat_eol == CRLF_AT_END) { - // fix it, since we're missing an LF that we want. - to_translate.insert(i, "\r"); - i++; - } - } - last_was_lf = false; - } else { - // not the two power characters. - last_was_lf = false; - } - } -} - -bool parser_bits::is_hexadecimal(char look_at) -{ - return range_check(look_at, 'a', 'f') - || range_check(look_at, 'A', 'F') - || range_check(look_at, '0', '9'); -} - -bool parser_bits::is_hexadecimal(const char *look_at, int len) -{ - for (int i = 0; i < len; i++) - if (!is_hexadecimal(look_at[i])) return false; - return true; -} - -bool parser_bits::is_hexadecimal(const astring &look_at, int len) -{ return is_hexadecimal(look_at.observe(), len); } - -bool parser_bits::is_alphanumeric(char look_at) -{ - return range_check(look_at, 'a', 'z') - || range_check(look_at, 'A', 'Z') - || range_check(look_at, '0', '9'); -} - -bool parser_bits::is_alphanumeric(const char *look_at, int len) -{ - for (int i = 0; i < len; i++) - if (!is_alphanumeric(look_at[i])) return false; - return true; -} - -bool parser_bits::is_alphanumeric(const astring &look_at, int len) -{ return is_alphanumeric(look_at.observe(), len); } - -bool parser_bits::is_identifier(char look_at) -{ - return range_check(look_at, 'a', 'z') - || range_check(look_at, 'A', 'Z') - || range_check(look_at, '0', '9') - || (look_at == '_'); -} - -bool parser_bits::is_identifier(const char *look_at, int len) -{ - if (is_numeric(look_at[0])) return false; - for (int i = 0; i < len; i++) - if (!is_identifier(look_at[i])) return false; - return true; -} - -bool parser_bits::is_identifier(const astring &look_at, int len) -{ return is_identifier(look_at.observe(), len); } - -bool parser_bits::is_numeric(char look_at) -{ - return range_check(look_at, '0', '9') || (look_at == '-'); -} - -bool parser_bits::is_numeric(const char *look_at, int len) -{ - for (int i = 0; i < len; i++) { - if (!is_numeric(look_at[i])) return false; - if ( (i > 0) && (look_at[i] == '-') ) return false; - } - return true; -} - -bool parser_bits::is_numeric(const astring &look_at, int len) -{ return is_numeric(look_at.observe(), len); } - -astring parser_bits::substitute_env_vars(const astring &to_process, - bool leave_unknown) -{ - astring editing = to_process; - -//LOG(astring("input to subst env: ") + to_process); - - int indy; // index of the dollar sign in the string. - while (true) { - indy = editing.find('$'); - if (negative(indy)) break; // all done. - int q; - for (q = indy + 1; q < editing.length(); q++) { - if (!parser_bits::is_identifier(editing[q])) - break; // done getting variable name. - } - if (q != indy + 1) { - // we caught something in our environment variable trap... - astring var_name = editing.substring(indy + 1, q - 1); -//LOG(astring("var name ") + var_name); - astring value_found = environment::get(var_name); -//LOG(astring("val found ") + value_found); - if (value_found.t()) { - editing.zap(indy, q - 1); - editing.insert(indy, value_found); - } else { - if (leave_unknown) { - // that lookup failed. let's mark it. - editing[indy] = '?'; - // simple replacement, shows variables that failed. - } else { - // replace it with blankness. - editing.zap(indy, q - 1); - } - } - } else { - // well, we didn't see a valid variable name, but we don't want to leave - // the dollar sign in there. - editing[indy] = '!'; // simple replacement, marks where syntax is bad. - } - - } - -//LOG(astring("output from subst env: ") + editing); - - return editing; -} - -} //namespace. - diff --git a/core/library/textual/parser_bits.h b/core/library/textual/parser_bits.h deleted file mode 100644 index 6ca2a21d..00000000 --- a/core/library/textual/parser_bits.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef PARSER_BITS_CLASS -#define PARSER_BITS_CLASS - -/*****************************************************************************\ -* * -* Name : parser_bits * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace textual { - -//! Warehouses some functions that are often useful during text parsing. - -class parser_bits -{ -public: - //! Line endings is an enumeration of the separator character(s) used for text files. - /*! on unix, every line in a text file has a line feed (LF) character appended to the line. - on ms-dos and ms-windows, each line has a carriage return (CR) and line feed (LF) appended - instead. a synonym for the line_ending is "eol" which stands for "end of line". */ - enum line_ending { - LF_AT_END = -15, //!< Unix standard is LF_AT_END ("\n"). - CRLF_AT_END, //!< DOS standard is CRLF_AT_END ("\r\n"). - NO_ENDING //!< No additional characters added as line endings. - }; - - static const char *eol_to_chars(line_ending ending); - //!< returns the C string form for the "ending" value. - - static line_ending platform_eol(); - //!< provides the appropriate ending on the current OS platform. - - static const char *platform_eol_to_chars(); - //!< provides the characters that make up this platform's line ending. - - static void translate_CR_for_platform(basis::astring &to_translate); - //!< flips embedded EOL characters for this platform's needs. - /*!< runs through the string "to_translate" and changes any CR or CRLF - combinations into the EOL (end-of-line) character that's appropriate - for this operating system. */ - - static basis::astring substitute_env_vars(const basis::astring &text, - bool leave_unknown = true); - //!< resolves embedded environment variables in "text". - /*!< replaces the names of any environment variables in "text" with the - variable's value and returns the resulting string. the variable names - are marked by a single dollar before an alphanumeric identifier - (underscores are valid), for example: $PATH if the "leave_unknown" flag - is true, then any unmatched variables are left in the text with a question - mark instead of a dollar sign. if it's false, then they are simply - replaced with nothing at all. */ - - static bool is_printable_ascii(char to_check); - //!< returns true if "to_check" is a normally visible ASCII character. - /*!< this is defined very simply by it being within the range of 32 to - 126. that entire range should be printable in ASCII. before 32 we have - control characters. after 126 we have potentially freakish looking - characters. this is obviously not appropriate for utf-8 or unicode. */ - - static bool white_space_no_cr(char to_check); - //!< reports if "to_check" is white space but not a carriage return. - /*!< returns true if the character "to_check" is considered a white space, - but is not part of an end of line combo (both '\n' and '\r' are - disallowed). the allowed set includes tab ('\t') and space (' ') only. */ - - static bool is_eol(char to_check); - //!< returns true if "to_check" is part of an end-of-line sequence. - /*!< this returns true for both the '\r' and '\n' characters. */ - - static bool white_space(char to_check); - //!< returns true if the character "to_check" is considered a white space. - /*!< this set includes tab ('\t'), space (' '), carriage return ('\n'), - and line feed ('\r'). */ - - static bool is_alphanumeric(char look_at); - //!< returns true if "look_at" is one of the alphanumeric characters. - /*!< This includes a to z in either case and 0 to 9. */ - static bool is_alphanumeric(const char *look_at, int len); - //!< returns true if the char ptr "look_at" is all alphanumeric characters. - static bool is_alphanumeric(const basis::astring &look_at, int len); - //!< returns true if the string "look_at" is all alphanumeric characters. - - static bool is_numeric(char look_at); - //!< returns true if "look_at" is a valid numerical character. - /*! this allows the '-' character for negative numbers also (but only for - first character if the char* or astring versions are used). does not - support floating point numbers or exponential notation yet. */ - static bool is_numeric(const char *look_at, int len); - //!< returns true if "look_at" is all valid numerical characters. - static bool is_numeric(const basis::astring &look_at, int len); - //!< returns true if the "look_at" string has only valid numerical chars. - - static bool is_hexadecimal(char look_at); - //!< returns true if "look_at" is one of the hexadecimal characters. - /*!< This includes a to f in either case and 0 to 9. */ - static bool is_hexadecimal(const char *look_at, int len); - //!< returns true if "look_at" is all hexadecimal characters. - static bool is_hexadecimal(const basis::astring &look_at, int len); - //!< returns true if the string "look_at" is all hexadecimal characters. - - static bool is_identifier(char look_at); - //!< returns true if "look_at" is a valid identifier character. - /*!< this just allows alphanumeric characters and underscore. */ - static bool is_identifier(const char *look_at, int len); - //!< returns true if "look_at" is composed of valid identifier character. - /*!< additionally, identifiers cannot start with a number. */ - static bool is_identifier(const basis::astring &look_at, int len); - //!< like is_identifier() above but operates on a string. -}; - -} //namespace. - -#endif - diff --git a/core/library/textual/string_convert.h b/core/library/textual/string_convert.h deleted file mode 100644 index 697439e7..00000000 --- a/core/library/textual/string_convert.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef STRING_CONVERSION_GROUP -#define STRING_CONVERSION_GROUP - -/*****************************************************************************\ -* * -* Name : string_convert * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include - -#ifdef __WIN32__ - #ifndef _MANAGED - #ifndef __MINGW32__ - #define _WINSOCKAPI_ // the dance of the windows headers. - #include - #include - #endif - #endif -#endif - -// forward. -class _bstr_t; // ATL (Active Template Library) string type. - -//! A collection of conversions between popular string types. - -namespace string_convert -{ - -#ifdef _AFXDLL - //! conversion from MFC CString to astring. - inline astring to_astring(const CString &original) - { return astring(from_unicode_temp(original)); } - - //! conversion from astring to MFC CString. - inline CString to_CString(const astring &original) - { return CString(to_unicode_temp(original)); } -#endif - -#ifdef WIN32 - #ifndef _MANAGED - #ifndef __MINGW32__ - //! conversion from ATL's _bstr_t object to astring. - inline basis::astring to_astring(const _bstr_t &original) { - return basis::astring(basis::astring::UNTERMINATED, (const char *)original, - original.length()); - } - - //! conversion from astring to the ATL _bstr_t object. - inline _bstr_t to_bstr_t(const basis::astring &original) - { return _bstr_t(original.s()); } - #endif - #endif -#endif - -//other conversions. - -} //namespace - -#endif - diff --git a/core/library/textual/string_manipulation.cpp b/core/library/textual/string_manipulation.cpp deleted file mode 100644 index 6e21691a..00000000 --- a/core/library/textual/string_manipulation.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/*****************************************************************************\ -* * -* Name : string_manipulation * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "parser_bits.h" -#include "string_manipulation.h" - -#include -#include -#include -#include - -using namespace basis; -using namespace mathematics; - -namespace textual { - -//SAFE_STATIC_CONST(astring_object, string_manipulation::splitter_finding_set, -// ("\t\r\n -,;?!.:")) -const char *splitter_finding_set = "\t\r\n -,;?!.:"; - // any of these characters make a valid place to break a line. - -astring string_manipulation::make_random_name(int min, int max) -{ - chaos rando; - int length = rando.inclusive(min, max); - // pick a size for the string. - astring to_return; - for (int i = 0; i < length; i++) { - int chah = rando.inclusive(0, 26); - // use a range one larger than alphabet size. - char to_add = 'a' + chah; - if (chah == 26) to_add = '_'; - // patch the extra value to be a separator. - to_return += to_add; - } - return to_return; -} - -astring string_manipulation::long_line(char line_item, int repeat) -{ return astring(line_item, repeat); } - -astring string_manipulation::indentation(int spaces) -{ - astring s; - for (int i = 0; i < spaces; i++) s += ' '; - return s; -} - -void string_manipulation::carriage_returns_to_spaces(astring &to_strip) -{ - for (int j = 0; j < to_strip.length(); j++) { - int original_j = j; // track where we started looking. - if (!parser_bits::is_eol(to_strip[j])) continue; - // we have found at least one CR. let's see what else there is. - if ( (to_strip[j] == '\r') && (to_strip[j + 1] == '\n') ) { - // this is looking like a DOS CR. let's skip that now. - j++; - } - j++; // skip the one we know is a CR. - if (parser_bits::is_eol(to_strip[j])) { - // we are seeing more than one carriage return in a row. let's - // truncate that down to just one. - j++; - while (parser_bits::is_eol(to_strip[j]) && (j < to_strip.length())) - j++; // skip to next one that might not be CR. - // now we think we know where there's this huge line of CRs. we will - // turn them all into spaces except the first. - to_strip[original_j] = '\n'; - for (int k = original_j + 1; k < j; k++) to_strip[k] = ' '; - // put the index back so we'll start looking at the non-CR char. - j--; - continue; // now skip back out to the main loop. - } else { - // we see only one carriage return, which we will drop in favor of - // joining those lines together. we iterate here since we might have - // seen a DOS CR taking up two spaces. - for (int k = original_j; k < j; k++) to_strip[k] = ' '; - } - } - -} - -void string_manipulation::split_lines(const astring &input_in, astring &output, - int min_column, int max_column) -{ - output = ""; - if (max_column - min_column + 1 < 2) return; // what's the point? - - astring input = input_in; // make a copy to work on. - carriage_returns_to_spaces(input); - - int col = min_column; - astring indent_add = indentation(min_column); - output = indent_add; // start with the extra space. - - bool just_had_break = false; - // set true if we just handled a line break in the previous loop. - bool put_accum_before_break = false; // true if we must postpone CR. - astring accumulated; - // holds stuff to print on next go-round. - - // now we parse across the list counting up our line size and making sure - // we don't go over it. - for (int j = 0; j < input.length(); j++) { - -//char to_print = input[j]; -//if (parser_bits::is_eol(to_print)) to_print = '_'; -//printf("[%d: val=%d, '%c', col=%d]\n", j, to_print, to_print, col); -//fflush(0); - - // handle the carriage return if it was ordered. - if (just_had_break) { - if (put_accum_before_break) { - output += accumulated; - // strip off any spaces from the end of the line. - output.strip_spaces(astring::FROM_END); - output += parser_bits::platform_eol_to_chars(); - accumulated = ""; - j++; // skip the CR that we think is there. - } - // strip off any spaces from the end of the line. - output.strip_spaces(astring::FROM_END); - output += parser_bits::platform_eol_to_chars(); - col = min_column; - output += indent_add; - just_had_break = false; - if (accumulated.length()) { - output += accumulated; - col += accumulated.length(); - accumulated = ""; - } - j--; - continue; - } - - put_accum_before_break = false; - - // skip any spaces we've got at the current position. - while ( (input[j] == ' ') || (input[j] == '\t') ) { - j++; - if (j >= input.length()) break; // break out of subloop if past it. - } - - if (j >= input.length()) break; // we're past the end. - - // handle carriage returns when they're at the current position. - char current_char = input[j]; - if (parser_bits::is_eol(current_char)) { - just_had_break = true; // set the state. - put_accum_before_break = true; - continue; - } - -//hmmm: the portion below could be called a find word break function. - - bool add_dash = false; // true if we need to break a word and add hyphen. - bool break_line = false; // true if we need to go to the next line. - bool invisible = false; // true if invisible characters were seen. - bool end_sentence = false; // true if there was a sentence terminator. - bool punctuate = false; // true if there was normal punctuation. - bool keep_on_line = false; // true if we want add current then break line. - char prior_break = '\0'; // set for real below. - char prior_break_plus_1 = '\0'; // ditto. - - // find where our next normal word break is, if possible. - int next_break = input.find_any(splitter_finding_set, j); - // if we didn't find a separator, just use the end of the string. - if (negative(next_break)) - next_break = input.length() - 1; - - // now we know where we're supposed to break, but we don't know if it - // will all fit. - prior_break = input[next_break]; - // hang onto the value before we change next_break. - prior_break_plus_1 = input[next_break + 1]; - // should still be safe since we're stopping before the last zero. - switch (prior_break) { - case '\r': case '\n': - break_line = true; - just_had_break = true; - put_accum_before_break = true; - // intentional fall-through. - case '\t': case ' ': - invisible = true; - next_break--; // don't include it in what's printed. - break; - case '?': case '!': case '.': - end_sentence = true; - // if we see multiples of these, we count them as just one. - while ( (input[next_break + 1] == '?') - || (input[next_break + 1] == '!') - || (input[next_break + 1] == '.') ) { - next_break++; - } - // make sure that there's a blank area after the supposed punctuation. - if (!parser_bits::white_space(input[next_break + 1])) - end_sentence = false; - break; - case ',': case ';': case ':': - punctuate = true; - // make sure that there's a blank area after the supposed punctuation. - if (!parser_bits::white_space(input[next_break + 1])) - punctuate = false; - break; - } - - // we'll need to add some spaces for certain punctuation. - int punct_adder = 0; - if (punctuate || invisible) punct_adder = 1; - if (end_sentence) punct_adder = 2; - - // check that we're still in bounds. - int chars_added = next_break - j + 1; - if (col + chars_added + punct_adder > max_column + 1) { - // we need to break before the next breakable character. - break_line = true; - just_had_break = true; - if (col + chars_added <= max_column + 1) { - // it will fit without the punctuation spaces, which is fine since - // it should be the end of the line. - invisible = false; - punctuate = false; - end_sentence = false; - punct_adder = 0; - keep_on_line = true; - } else if (min_column + chars_added > max_column + 1) { - // this word won't ever fit unless we break it. - int chars_left = max_column - col + 1; - // remember to take out room for the dash also. - if (chars_left < 2) { - j--; // stay where we are. - continue; - } else { - next_break = j + chars_left - 2; - chars_added = next_break - j + 1; - if (next_break >= input.length()) - next_break = input.length() - 1; - else if (next_break < j) - next_break = j; - add_dash = true; - } - } - } - - astring adding_chunk = input.substring(j, next_break); - // this is what we've decided the next word chunk to be added will be. - // we still haven't completely decided where it goes. - - if (break_line) { - col = min_column; - if (add_dash || keep_on_line) { - // include the previous stuff on the same line. - output += adding_chunk; - if (add_dash) output += "-"; - j = next_break; - continue; // done with this case. - } - - // don't include the previous stuff; make it go to the next line. - accumulated = adding_chunk; - if (punctuate || invisible) { - accumulated += " "; - } else if (end_sentence) { - accumulated += " "; - } - j = next_break; - continue; - } - - // add the line normally since it should fit. - output += adding_chunk; - col += chars_added + punct_adder; // add the characters added. - j = next_break; - just_had_break = false; // reset the state. - - // handle when we processed an invisible or punctuation character. - if (punctuate || invisible) { - output += " "; - } else if (end_sentence) { - output += " "; - } - } - // make sure we handle any leftovers. - if (accumulated.length()) { - output.strip_spaces(astring::FROM_END); - output += parser_bits::platform_eol_to_chars(); - output += indent_add; - output += accumulated; - } - output.strip_spaces(astring::FROM_END); - output += parser_bits::platform_eol_to_chars(); -} - -char string_manipulation::hex_to_char(abyte to_convert) -{ - if (to_convert <= 9) return char('0' + to_convert); - else if ( (to_convert >= 10) && (to_convert <= 15) ) - return char('A' - 10 + to_convert); - else return '?'; -} - -abyte string_manipulation::char_to_hex(char to_convert) -{ - if ( (to_convert >= '0') && (to_convert <= '9') ) - return char(to_convert - '0'); - else if ( (to_convert >= 'a') && (to_convert <= 'f') ) - return char(to_convert - 'a' + 10); - else if ( (to_convert >= 'A') && (to_convert <= 'F') ) - return char(to_convert - 'A' + 10); - else return 0; -} - -byte_array string_manipulation::string_to_hex(const astring &to_convert) -{ - byte_array to_return(0, NIL); - for (int i = 0; i < to_convert.length() / 2; i++) { - int str_index = i * 2; - abyte first_byte = char_to_hex(to_convert.get(str_index)); - abyte second_byte = char_to_hex(to_convert.get(str_index + 1)); - abyte to_stuff = abyte(first_byte * 16 + second_byte); - to_return.concatenate(to_stuff); - } - return to_return; -} - -astring string_manipulation::hex_to_string(const byte_array &to_convert) -{ - astring to_return; - for (int i = 0; i < to_convert.length() * 2; i += 2) { - int str_index = i / 2; - char first_char = hex_to_char(char(to_convert.get(str_index) / 16)); - char second_char = hex_to_char(char(to_convert.get(str_index) % 16)); - to_return += astring(first_char, 1); - to_return += astring(second_char, 1); - } - return to_return; -} - -} //namespace. - diff --git a/core/library/textual/string_manipulation.h b/core/library/textual/string_manipulation.h deleted file mode 100644 index c240f0fb..00000000 --- a/core/library/textual/string_manipulation.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef STRING_MANIPULATION_CLASS -#define STRING_MANIPULATION_CLASS - -/*****************************************************************************\ -* * -* Name : string_manipulation * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -namespace textual { - -//! Provides various functions for massaging strings. - -class string_manipulation -{ -public: - -////////////// - - static basis::astring make_random_name(int min = 1, int max = 64); - //!< creates a random name, where the letters are between 'a' and 'z'. - /*!< the underscore will also be used occasionally. the size is random, - but the minimum size is "min" while the maximum is "max". */ - -////////////// - - static basis::astring long_line(char line_item = '/', int repeat = 76); - //!< produces a long line of "line_item" characters. - /*!< returns a string of text that is somewhat long compared to an 80 column - output window and which consists of a single character repeated. the - character used and the repeat count are both variable. */ - - static basis::astring indentation(int spaces); - //!< Returns a string made of white space that is "spaces" long. - -////////////// - - static void carriage_returns_to_spaces(basis::astring &to_strip); - //!< converts carriage returns in "to_strip" into spaces. - /*!< processes the string "to_strip" by replacing all single carriage - returns with spaces and by turning two or more carriage returns into a - single CR plus spaces. */ - -////////////// - - static void split_lines(const basis::astring &input, basis::astring &output, - int min_column = 0, int max_column = 79); - //!< formats blocks of text for a maximum width. - /*!< processes the "input" text by splitting any lines that are longer - than the "max_column". the "min_column" is used to specify how much - indentation should be included. */ - -////////////// - - // numerical manipulation functions: - - static basis::abyte char_to_hex(char to_convert); - //!< Converts a single character into the corresponding hex nibble. - /*!< If the character is not valid, an arbitrary value is returned. */ - static char hex_to_char(basis::abyte to_convert); - //!< Converts a byte between 0 and 15 into a corresponding hexadecimal character. - - static basis::byte_array string_to_hex(const basis::astring &character_form); - //!< Turns a string form of a set of hex numbers into an array of bytes. - /*!< This functions takes a string in "character_form" and returns an array - of bytes that is half as long and which contains the hexadecimal - interpretation of the string. This is currently geared to even length - strings... */ - static basis::astring hex_to_string(const basis::byte_array &byte_form); - //!< The inverse of string_to_hex prints "byte_form" as text. - /*!< This function takes an array of bytes and converts them into their - equivalent hexadecimal character representation. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/textual/xml_generator.cpp b/core/library/textual/xml_generator.cpp deleted file mode 100644 index ec7645cf..00000000 --- a/core/library/textual/xml_generator.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/*****************************************************************************\ -* * -* Name : xml_generator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "parser_bits.h" -#include "string_manipulation.h" -#include "xml_generator.h" - -#include -#include -#include - -using namespace basis; -using namespace structures; - -namespace textual { - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s); - -////////////// - -class tag_info -{ -public: - astring _tag_name; - string_table _attribs; - - tag_info() {} - - tag_info(const astring &tag_name, const string_table &attribs) - : _tag_name(tag_name), _attribs(attribs) {} -}; - -////////////// - -class tag_stack : public stack -{ -public: - tag_stack() : stack(0) {} -}; - -////////////// - -xml_generator::xml_generator(int mods) -: _tags(new tag_stack), - _accumulator(new astring), - _human_read(mods & HUMAN_READABLE), - _clean_chars(mods & CLEAN_ILLEGAL_CHARS), - _indentation(2) -{ -} - -xml_generator::~xml_generator() -{ - WHACK(_tags); - WHACK(_accumulator); -} - -const char *xml_generator::outcome_name(const outcome &to_name) -{ - switch (to_name.value()) { - case ERRONEOUS_TAG: return "ERRONEOUS_TAG"; - default: return common::outcome_name(to_name); - } -} - -void xml_generator::set_indentation(int to_indent) -{ - if (to_indent <= 0) to_indent = 1; - _indentation = to_indent; -} - -void xml_generator::reset() -{ - _accumulator->reset(); -// we need a reset on stack. - while (_tags->pop() == common::OKAY) {} -} - -astring xml_generator::generate() -{ - astring to_return; - generate(to_return); - return to_return; -} - -void xml_generator::generate(astring &generated) -{ - close_all_tags(); - generated = ""; // first string is the version. - if (_human_read) generated += parser_bits::platform_eol_to_chars(); - generated += *_accumulator; -} - -outcome xml_generator::open_tag(const astring &tag_name) -{ - string_table junk; - return open_tag(tag_name, junk); -} - -outcome xml_generator::add_header(const astring &tag_name, - const string_table &attributes) -{ - tag_info new_item(tag_name, attributes); - print_open_tag(new_item, HEADER_TAG); - return OKAY; -} - -outcome xml_generator::open_tag(const astring &tag_name, const string_table &attributes) -{ - tag_info new_item(tag_name, attributes); - print_open_tag(new_item); - _tags->push(new_item); - return OKAY; -} - -outcome xml_generator::close_tag(const astring &tag_name) -{ - if (_tags->elements() < 1) return NOT_FOUND; - // check to see that it's the right one to close. - if (_tags->top()._tag_name != tag_name) return ERRONEOUS_TAG; - print_close_tag(tag_name); - _tags->pop(); - return OKAY; -} - -void xml_generator::close_all_tags() -{ - while (_tags->elements()) { - close_tag(_tags->top()._tag_name); - } -} - -outcome xml_generator::add_content(const astring &content) -{ - if (_human_read) { - astring indentata = string_manipulation::indentation(_indentation); - int num_indents = _tags->elements(); - for (int i = 0; i < num_indents; i++) - *_accumulator += indentata; - } - if (_clean_chars) - *_accumulator += clean_reserved(content); - else - *_accumulator += content; - if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); - return OKAY; -} - -void xml_generator::print_open_tag(const tag_info &to_print, int type) -{ - bool is_header = false; - if (type == HEADER_TAG) is_header = true; - - if (_human_read) { -//hmmm: starting to look like a nice macro for this stuff, param is num levs. - astring indentata = string_manipulation::indentation(_indentation); - int num_indents = _tags->elements(); - for (int i = 0; i < num_indents; i++) - *_accumulator += indentata; - } - - if (is_header) - *_accumulator += ""; - else - *_accumulator += ">"; - if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); -} - -void xml_generator::print_close_tag(const astring &tag_name) -{ - if (_human_read) { - astring indentata = string_manipulation::indentation(_indentation); - int num_indents = _tags->elements() - 1; - for (int i = 0; i < num_indents; i++) - *_accumulator += indentata; - } - *_accumulator += ""; - if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); -} - -#define PLUGIN_REPLACEMENT(posn, repl_string) { \ - to_modify.zap(posn, posn); \ - to_modify.insert(posn, repl_string); \ - posn += int(strlen(repl_string)) - 1; \ -} - -void xml_generator::clean_reserved_mod(astring &to_modify, - bool replace_spaces) -{ -//could this set live somewhere? - const char *quot = """; - const char *amp = "&"; - const char *lt = "<"; - const char *gt = ">"; - const char *apos = "'"; - const char *space = "_"; - // was going to use %20 but that still won't parse in an attribute name. - - for (int i = 0; i < to_modify.length(); i++) { - switch (to_modify[i]) { - case '"': PLUGIN_REPLACEMENT(i, quot); break; - case '&': PLUGIN_REPLACEMENT(i, amp); break; - case '<': PLUGIN_REPLACEMENT(i, lt); break; - case '>': PLUGIN_REPLACEMENT(i, gt); break; - case '\'': PLUGIN_REPLACEMENT(i, apos); break; - case ' ': if (replace_spaces) PLUGIN_REPLACEMENT(i, space); break; - default: continue; - } - } -} - -astring xml_generator::clean_reserved(const astring &to_modify, - bool replace_spaces) -{ - astring to_return = to_modify; - clean_reserved_mod(to_return, replace_spaces); - return to_return; -} - -} //namespace. - - diff --git a/core/library/textual/xml_generator.h b/core/library/textual/xml_generator.h deleted file mode 100644 index 0b3901a6..00000000 --- a/core/library/textual/xml_generator.h +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef XML_GENERATOR_CLASS -#define XML_GENERATOR_CLASS - -/*****************************************************************************\ -* * -* Name : xml_generator * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace textual { - -class tag_info; -class tag_stack; - -//! Supports simple XML output with consistency checking. - -class xml_generator -{ -public: - enum behavioral_mods { - HUMAN_READABLE = 0x1, - CLEAN_ILLEGAL_CHARS = 0x2 - }; - - xml_generator(int modifiers = HUMAN_READABLE | CLEAN_ILLEGAL_CHARS); - //!< creates an xml generator with the specified behavior. - - virtual ~xml_generator(); - - DEFINE_CLASS_NAME("xml_generator"); - - //! the possible ways that operations here can complete. - enum outcomes { - OKAY = basis::common::OKAY, - NOT_FOUND = basis::common::NOT_FOUND, - ERRONEOUS_TAG = basis::common::INVALID //temporary until we can shed a compatibility concern. -// DEF INE_OUTCOME(ERRONEOUS_TAG, -75, "The most recently opened tag must be " -// "closed before a new tag can be opened and before any other tag can " -// "be closed"), - }; - - static const char *outcome_name(const basis::outcome &to_name); - //!< reports the string version of "to_name". - - void reset(); //!< throws out all accumulated information. - - basis::astring generate(); - //!< writes the current state into a string and returns it. - /*!< if there was an error during generation, the string will be empty. - note that unclosed tags are not considered an error; they will simply be - closed. note that the accumulated string is not cleared after the - generate() invocation. use reset() to clear out all prior state. */ - - void generate(basis::astring &generated); - //!< synonym method, writes the current state into "generated". - - basis::outcome add_header(const basis::astring &tag_name, const structures::string_table &attributes); - //!< adds an xml style header with the "tag_name" and "attributes". - /*!< headers can be located anywhere in the file. */ - - basis::outcome open_tag(const basis::astring &tag_name, const structures::string_table &attributes); - //!< adds a tag with "tag_name" and the "attributes", if any. - /*!< this adds an item into the output string in the form: @code - @endcode - it is required that you close the tag later on, after the tag's contents - have been added. */ - - basis::outcome open_tag(const basis::astring &tag_name); - //!< adds a tag with "tag_name" without any attributes. - - basis::outcome close_tag(const basis::astring &tag_name); - //!< closes a previously added "tag_name". - /*!< this will generate xml code like so: @code - @endcode - note that it is an error to try to close any tag but the most recently - opened one. */ - - void close_all_tags(); - //!< a wide-bore method that closes all outstanding tags. - - basis::outcome add_content(const basis::astring &content); - //!< stores content into the currently opened tag. - /*!< it is an error to add content when no tag is open. */ - - void set_indentation(int to_indent); - //!< sets the number of spaces to indent for the human readable form. - - static basis::astring clean_reserved(const basis::astring &to_modify, - bool replace_spaces = false); - //!< returns a cleaned version of "to_modify" to make it XML appropriate. - /*!< if "replace_spaces" is true, then any spaces will be turned into - their html code equivalent; this helps in attribute names. */ - - static void clean_reserved_mod(basis::astring &to_modify, - bool replace_spaces = false); - //!< ensures that "to_modify" contains only characters valid for XML. - /*!< this is only different from the other clean method because this - one modifies the string in place. */ - -private: - tag_stack *_tags; //!< the already opened tags. - basis::astring *_accumulator; //!< stores our output. - bool _human_read; //!< true if the output should be human readable. - bool _clean_chars; //!< true if strings should be validated and repaired. - int _indentation; //!< number of spaces per level of xml. - - enum open_types { NORMAL_TAG, HEADER_TAG }; - void print_open_tag(const tag_info &to_print, int type = NORMAL_TAG); - //!< opens the tag for to_print by showing the tag name and attributes. - void print_close_tag(const basis::astring &tag_name); - //!< closes the tag for "tag_name" in the output string. -}; - -} //namespace. - -#endif - diff --git a/core/library/textual/xml_parser.cpp b/core/library/textual/xml_parser.cpp deleted file mode 100644 index f20806db..00000000 --- a/core/library/textual/xml_parser.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/*****************************************************************************\ -* * -* Name : xml_parser * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "xml_parser.h" - -#include -#include - -using namespace basis; -using namespace structures; - -namespace textual { - -xml_parser::xml_parser(const astring &to_parse) -{ - if (!to_parse) {} -} - -xml_parser::~xml_parser() -{ -} - -const char *xml_parser::outcome_name(const outcome &to_name) -{ - return common::outcome_name(to_name); -} - -void xml_parser::reset(const astring &to_parse) -{ - if (!to_parse) {} -} - -outcome xml_parser::header_callback(astring &header_name, - string_table &attributes) -{ - if (!header_name || !attributes.symbols()) {} - return common::OKAY; -} - - -outcome xml_parser::tag_open_callback(astring &tag_name, - string_table &attributes) -{ - if (!tag_name || !attributes.symbols()) {} - return OKAY; -} - -outcome xml_parser::tag_close_callback(astring &tag_name) -{ - if (!tag_name) {} - return OKAY; -} - -outcome xml_parser::content_callback(astring &content) -{ - if (!content) {} - return OKAY; -} - -outcome xml_parser::parse() -{ - -//phases: we are initially always seeking a bracket bounded tag of some sort. - -// the first few constructs must be headers, especially the xml header. - -// is it true that headers are the only valid thing to see before real tags -// start, or can there be raw content embedded in there? -// yes, it seems to be true in mozilla. you can't have bare content in -// between the headers and the real tags. - -// actually, if we allow the file to not start with the xml header and -// version, then that's a bug. - -// need function to accumulate the tag based on structure. do headers -// have to have a ? as part of the inner and outer structure? - -// test against mozilla to ensure we are seeing the same things; get -// together some tasty sample files. - -// count lines and chars so we can report where it tanked. - -// back to phases, (not a precise grammar below) -// white_space ::= [ ' ' '\n' '\r' '\t' ] * -// ws ::= white_space -// text_phrase ::= not_white_space_nor_reserved not_reserved* -// name ::= text_phrase -// value ::= not_reserved * -// lt_char ::= '<' -// quote ::= '"' - -// xml_file ::= header+ ws tagged_unit+ ws -// header ::= '<' '?' name ws attrib_list ws '?' '>' ws -// tagged_unit ::= open_tag content* close_tag ws -// content ::= [ tagged_unit | text_phrase ] + ws -// open_tag ::= '<' name ws attrib_list ws '>' ws -// attrib_list ::= ( attribute ws ) * ws -// attribute ::= name ws '=' ws quoted_string ws -// quoted_string ::= '"' not_lt_char_nor_quote '"' ws -// close_tag :: '<' '/' name ws '>' ws - -//write a recursive descent parser on this grammar and spit out the -// productions as callbacks, at least for the major ones already listed. - -return common::NOT_IMPLEMENTED; -} - -/* callbacks to invoke. -outcome header_callback(astring &header_name, string_table &attributes) -outcome tag_open_callback(astring &tag_name, string_table &attributes) -outcome tag_close_callback(astring &tag_name) -outcome content_callback(astring &content) -*/ - -} //namespace. - diff --git a/core/library/textual/xml_parser.h b/core/library/textual/xml_parser.h deleted file mode 100644 index 7b2011d8..00000000 --- a/core/library/textual/xml_parser.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef XML_PARSER_CLASS -#define XML_PARSER_CLASS - -/*****************************************************************************\ -* * -* Name : xml_parser * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include - -// forward. -#include -#include - -namespace textual { - -//! Parses XML input and invokes a callback for the different syntactic pieces. - -// hmmm, could this be the first class ever named this? perhaps it should be -// in a textual namespace. -->after current sprint. - -class xml_parser -{ -public: - xml_parser(const basis::astring &to_parse); - virtual ~xml_parser(); - - DEFINE_CLASS_NAME("xml_parser"); - - //! the possible ways that operations here can complete. - enum outcomes { - OKAY = basis::common::OKAY -//uhhh... - }; - - static const char *outcome_name(const basis::outcome &to_name); - //!< reports the string version of "to_name". - - void reset(const basis::astring &to_parse); - //!< throws out any accumulated information and uses "to_parse" instead. - - basis::outcome parse(); - //!< starts the parsing process on the current string. - /*!< this will cause callbacks to be invoked for each of the xml syntactic - elements. */ - - virtual basis::outcome header_callback(basis::astring &header_name, - structures::string_table &attributes); - //!< invoked when a well-formed xml header is seen in the input stream. - /*!< the following applies to all of the callbacks: the derived method must - return an outcome, which will be used by the parser. if the outcome is - OKAY, then parsing will continue. any other outcome will cause parsing - to stop and will become the return value of the parse() method. */ - - virtual basis::outcome tag_open_callback(basis::astring &tag_name, - structures::string_table &attributes); - //!< an xml tag has been opened in the input stream. - - virtual basis::outcome tag_close_callback(basis::astring &tag_name); - //!< an xml tag was closed in the input stream. - - virtual basis::outcome content_callback(basis::astring &content); - //!< invoked when plain text content is found inside an opened tag. - -private: - basis::astring *_xml_stream; // the stringful of xml information. - -}; - -} //namespace. - -#endif - diff --git a/core/library/timely/earth_time.cpp b/core/library/timely/earth_time.cpp deleted file mode 100644 index ed7c4583..00000000 --- a/core/library/timely/earth_time.cpp +++ /dev/null @@ -1,406 +0,0 @@ -/*****************************************************************************\ -* * -* Name : earth_time * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1999-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "earth_time.h" - -#include -#include - -#include -#if defined(__WIN32__) || defined(__UNIX__) - #include -#endif - -using namespace basis; -using namespace structures; - -namespace timely { - -const int days_in_month[12] - = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -const int leap_days_in_month[12] - = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -const int julian_days_in_month[12] - = { 31, 29, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 }; -//hmmm: is this right? - -const int julian_leap_days_in_month[12] - = { 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 }; - -////////////// - -void clock_time::pack(byte_array &packed_form) const -{ - attach(packed_form, hour); - attach(packed_form, minute); - attach(packed_form, second); - attach(packed_form, millisecond); - attach(packed_form, microsecond); -} - -bool clock_time::unpack(byte_array &packed_form) -{ - if (!detach(packed_form, hour)) return false; - if (!detach(packed_form, minute)) return false; - if (!detach(packed_form, second)) return false; - if (!detach(packed_form, millisecond)) return false; - if (!detach(packed_form, microsecond)) return false; - return true; -} - -#define EASY_LT(x, y) \ - if (x < y) return true; \ - if (x > y) return false - -bool clock_time::operator < (const clock_time &to_compare) const -{ - EASY_LT(hour, to_compare.hour); - EASY_LT(minute, to_compare.minute); - EASY_LT(second, to_compare.second); - EASY_LT(millisecond, to_compare.millisecond); - EASY_LT(microsecond, to_compare.microsecond); - return false; -} - -bool clock_time::operator == (const clock_time &to_compare) const -{ - return (hour == to_compare.hour) - && (minute == to_compare.minute) - && (second == to_compare.second) - && (millisecond == to_compare.millisecond) - && (microsecond == to_compare.microsecond); -} - -astring clock_time::text_form(int how) const -{ - astring to_return; - text_form(to_return, how); - return to_return; -} - -void clock_time::text_form(astring &to_return, int how) const -{ - if (!how) return; // enforce use of the default. - if (how & MILITARY) - to_return += a_sprintf("%02d:%02d", hour, minute); - else { - int uhr = hour; - if (uhr > 12) uhr -= 12; - to_return += a_sprintf("%2d:%02d", uhr, minute); - } - if ( (how & SECONDS) || (how & MILLISECONDS) ) - to_return += a_sprintf(":%02d", second); - if (how & MILLISECONDS) - to_return += a_sprintf(":%03d", millisecond); - if (how & MERIDIAN) { - if (hour >= 12) to_return += "PM"; - else to_return += "AM"; - } -} - -// makes sure that "val" is not larger than "max". if it is, then max is -// used as a divisor and stored in "rolls". -#define limit_value(val, max) \ - if (val < 0) { \ - rolls = val / max; \ - rolls--; /* subtract an extra one since we definitely roll before -max */ \ - val += max * -rolls; \ - } else if (val >= max) { \ - rolls = val / max; \ - val -= max * rolls; \ - } else { rolls = 0; } - -int clock_time::normalize(clock_time &to_fix) -{ - int rolls = 0; // rollover counter. - limit_value(to_fix.microsecond, 1000); - to_fix.millisecond += rolls; - limit_value(to_fix.millisecond, 1000); - to_fix.second += rolls; - limit_value(to_fix.second, 60); - to_fix.minute += rolls; - limit_value(to_fix.minute, 60); - to_fix.hour += rolls; - limit_value(to_fix.hour, 24); - return rolls; -} - -////////////// - -void day_in_year::pack(byte_array &packed_form) const -{ - attach(packed_form, day_of_year); - attach(packed_form, abyte(day_of_week)); - attach(packed_form, abyte(month)); - attach(packed_form, day_in_month); - attach(packed_form, abyte(1)); - // still packing dst chunk; must for backward compatibility. -} - -bool day_in_year::unpack(byte_array &packed_form) -{ - if (!detach(packed_form, day_of_year)) return false; - abyte temp; - if (!detach(packed_form, temp)) return false; - day_of_week = days(temp); - if (!detach(packed_form, temp)) return false; - month = months(temp); - if (!detach(packed_form, day_in_month)) return false; - if (!detach(packed_form, temp)) return false; // dst chunk--backward compat. - return true; -} - -bool day_in_year::operator < (const day_in_year &to_compare) const -{ - EASY_LT(month, to_compare.month); - EASY_LT(day_in_month, to_compare.day_in_month); - return false; -} - -bool day_in_year::operator == (const day_in_year &to_compare) const -{ - return (month == to_compare.month) - && (day_in_month == to_compare.day_in_month); -} - -astring day_in_year::text_form(int how) const -{ - astring to_return; - text_form(to_return, how); - return to_return; -} - -void day_in_year::text_form(astring &to_stuff, int how) const -{ - if (!how) return; // enforce use of the default. - if (how & INCLUDE_DAY) to_stuff += astring(day_name(day_of_week)) + " "; - const char *monat = short_month_name(month); - if (how & LONG_MONTH) - monat = month_name(month); -//hmmm: more formatting, like euro? - to_stuff += monat; - to_stuff += a_sprintf(" %02d", day_in_month); -} - -// note: this only works when adjusting across one month, not multiples. -int limit_day_of_month(int &day, int days_in_month, int days_in_prev_month) -{ - if (day > days_in_month) { - day -= days_in_month; - return 1; // forward rollover. - } else if (day < 1) { - day += days_in_prev_month; - return -1; - } - return 0; // no rolling. -} - -int day_in_year::normalize(day_in_year &to_fix, bool leap_year) -{ - int rolls = 0; // rollover counter. - int daysinm = leap_year? - leap_days_in_month[to_fix.month] : days_in_month[to_fix.month]; - int prev_month = to_fix.month - 1; - if (prev_month < 0) prev_month = 11; - int daysinpm = leap_year? - leap_days_in_month[prev_month] : days_in_month[prev_month]; - rolls = limit_day_of_month(to_fix.day_in_month, daysinm, daysinpm); - int monat = to_fix.month + rolls; - limit_value(monat, 12); // months are zero based. - to_fix.month = months(monat); - return rolls; -} - -////////////// - -void time_locus::pack(byte_array &packed_form) const -{ - attach(packed_form, year); - clock_time::pack(packed_form); - day_in_year::pack(packed_form); -} - -bool time_locus::unpack(byte_array &packed_form) -{ - if (!detach(packed_form, year)) return false; - if (!clock_time::unpack(packed_form)) return false; - if (!day_in_year::unpack(packed_form)) return false; - return true; -} - -astring time_locus::text_form_long(int t, int d, int y) const -{ - astring to_return; - text_form_long(to_return, t, d, y); - return to_return; -} - -bool time_locus::equal_to(const equalizable &s2) const { - const time_locus *s2_cast = dynamic_cast(&s2); - if (!s2_cast) throw "error: time_locus::==: unknown type"; - return (year == s2_cast->year) - && ( (const day_in_year &) *this == *s2_cast) - && ( (const clock_time &) *this == *s2_cast); -} - -bool time_locus::less_than(const orderable &s2) const { - const time_locus *s2_cast = dynamic_cast(&s2); - if (!s2_cast) throw "error: time_locus::<: unknown type"; - EASY_LT(year, s2_cast->year); - if (day_in_year::operator < (*s2_cast)) return true; - if (!(day_in_year::operator == (*s2_cast))) return false; - if (clock_time::operator < (*s2_cast)) return true; - return false; -} - -void time_locus::text_form_long(astring &to_stuff, int t, int d, int y) const -{ -//hmmm: more formatting desired, like european. - if (!y) { - text_form_long(to_stuff, t, d); // enforce use of the default. - return; - } - // add the day. - day_in_year::text_form(to_stuff, d); - to_stuff += " "; - // add the year. - if (y & SHORT_YEAR) - to_stuff += a_sprintf("%2d", year % 100); - else - to_stuff += a_sprintf("%4d", year); - // add the time. - to_stuff += " "; - clock_time::text_form(to_stuff, t); -} - -int time_locus::normalize(time_locus &to_fix) -{ - int rolls = clock_time::normalize(to_fix); - to_fix.day_in_month += rolls; - -//hmmm: this little gem should be abstracted to a method. - bool leaping = !(to_fix.year % 4); - if (!(to_fix.year % 100)) leaping = false; - if (!(to_fix.year % 400)) leaping = true; - - rolls = day_in_year::normalize(to_fix, leaping); - to_fix.year += rolls; - return 0; - // is that always right? not for underflow. -//hmmm: resolve the issue of rollovers here. -} - -////////////// - -time_locus convert(const tm &to_convert, int ms) -{ - time_locus r; - - // we lack the resolution for this, currently. - r.microsecond = 0; - - r.second = to_convert.tm_sec; - r.minute = to_convert.tm_min; - r.hour = to_convert.tm_hour; - r.day_in_month = to_convert.tm_mday; - r.month = months(to_convert.tm_mon); - r.year = to_convert.tm_year + 1900; - r.day_of_week = days(to_convert.tm_wday); - r.day_of_year = to_convert.tm_yday; - r.millisecond = ms; - return r; -} - -time_locus now() -{ - timeb current; - ftime(¤t); - tm split_time(*localtime(¤t.time)); - return convert(split_time, current.millitm); -} - -time_locus greenwich_now() -{ - timeb current; - ftime(¤t); - tm split_time(*gmtime(¤t.time)); - return convert(split_time, current.millitm); -} - -clock_time time_now() { return now(); } - -days day_now() { return now().day_of_week; } - -months month_now() { return now().month; } - -int year_now() { return now().year; } - -day_in_year date_now() { return now(); } - -const char *day_name(days to_name) -{ - switch (to_name) { - case SUNDAY: return "Sunday"; - case MONDAY: return "Monday"; - case TUESDAY: return "Tuesday"; - case WEDNESDAY: return "Wednesday"; - case THURSDAY: return "Thursday"; - case FRIDAY: return "Friday"; - case SATURDAY: return "Saturday"; - default: return "Not_a_day"; - } -} - -const char *month_name(months to_name) -{ - switch (to_name) { - case JANUARY: return "January"; - case FEBRUARY: return "February"; - case MARCH: return "March"; - case APRIL: return "April"; - case MAY: return "May"; - case JUNE: return "June"; - case JULY: return "July"; - case AUGUST: return "August"; - case SEPTEMBER: return "September"; - case OCTOBER: return "October"; - case NOVEMBER: return "November"; - case DECEMBER: return "December"; - default: return "Not_a_month"; - } -} - -const char *short_month_name(months to_name) -{ - switch (to_name) { - case JANUARY: return "Jan"; - case FEBRUARY: return "Feb"; - case MARCH: return "Mar"; - case APRIL: return "Apr"; - case MAY: return "May"; - case JUNE: return "Jun"; - case JULY: return "Jul"; - case AUGUST: return "Aug"; - case SEPTEMBER: return "Sep"; - case OCTOBER: return "Oct"; - case NOVEMBER: return "Nov"; - case DECEMBER: return "Dec"; - default: return "Not"; - } -} - -} // namespace. - diff --git a/core/library/timely/earth_time.h b/core/library/timely/earth_time.h deleted file mode 100644 index 3b249413..00000000 --- a/core/library/timely/earth_time.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef EARTH_TIME_GROUP -#define EARTH_TIME_GROUP - -// Name : earth_time -// Author : Chris Koeritz -/****************************************************************************** -* Copyright (c) 1999-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -//! A set of methods for rendering calendrical and clock times. -/*! - It is based on the Gregorian calendar currently in use by the USA and other - countries. -*/ - -namespace timely { - - enum days { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }; - //!< The names of the days of the week. - - days day_now(); //!< Returns the current local day. - - const char *day_name(days to_name); - //!< Returns the name of the day "to_name". - - enum months { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, - SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER }; - //!< The names of the months in our calendar. - - months month_now(); //!< returns the local month. - - const char *month_name(months to_name); - //!< Returns the name of the month "to_name". - const char *short_month_name(months to_name); - //!< Returns a shorter, constant-length (3 characters) month name. - - extern const int days_in_month[12]; - //!< The number of days in each month in the standard year. - extern const int leap_days_in_month[12]; - //!< The number of days in each month in a leap year. - - extern const int julian_days_in_month[12]; - //!< Number of days in each month based on the julian calendar. - extern const int julian_leap_days_in_month[12]; - //!< Number of days in each month of a leap year in the julian calendar. - - const int SECONDS_IN_MINUTE = 60; //!< Number of seconds in one minute. - const int MINUTES_IN_HOUR = 60; //!< Number of minutes in an hour. - const int HOURS_IN_DAY = 24; //!< Number of hours in a day. - const int DAYS_IN_YEAR = 365; //!< Number of days in a standard year. - const int LEAP_DAYS_IN_YEAR = 366; //!< Number of days in a leap year. - const double APPROX_DAYS_IN_YEAR = 365.2424; - //!< A more accurate measure of the number of days in a year. - /*!< This is the more accurate mean length of time in 24 hour days between - vernal equinoxes. it's about 11 minutes shy of 365.25 days. */ - - //! An enumeration of time zones, both relative and absolute. - enum time_zones { - LOCAL_ZONE, //!< The time zone this computer is configured to report. - GREENWICH_ZONE //!< The time zone of Greenwich Mean Time. - }; - - // now some structures for representing time... - - //! A specific point in time as represented by a 24 hour clock. - class clock_time : public virtual basis::packable - { - public: - int hour; //!< The hour represented in military time: 0 through 23. - int minute; //!< The number of minutes after the hour. - int second; //!< The number of seconds after the current minute. - int millisecond; //!< The number of milliseconds elapsed in this second. - int microsecond; //!< Number of microseconds elapsed in this millisecond. - - //! Constructs a clock_time object given all the parts. - clock_time(int h = 0, int m = 0, int s = 0, int ms = 0, int us = 0) - : hour(h), minute(m), second(s), millisecond(ms), - microsecond(us) {} - ~clock_time() {} - - int packed_size() const { return 5 * structures::PACKED_SIZE_INT32; } - - virtual void pack(basis::byte_array &packed_form) const; - //!< Packs a clock time into an array of bytes. - virtual bool unpack(basis::byte_array &packed_form); - //!< Unpacks a clock time from an array of bytes. - - bool operator < (const clock_time &to_compare) const; - //!< Returns true if this clock_time is earlier than "to_compare" - bool operator == (const clock_time &to_compare) const; - //!< Returns true if this clock_time is equal to "to_compare" - - //! An enumeration of time formatting modes used when printing the time. - enum time_formats { - MERIDIAN = 0x1, //!< default: uses 12 hour with AM/PM and no seconds. - MILITARY = 0x2, //!< use military 24 hour time. - NO_AM_PM = 0x4, //!< use 12 hour time but don't include AM/PM. - SECONDS = 0x8, //!< include the number of seconds as a third field. - MILLISECONDS = 0x10 //!< milliseconds are fourth field (after secs). - }; - - basis::astring text_form(int how = MERIDIAN) const; - //!< Prints the clock_time according to "how". - /*!< "how" is a combination of time_formats. */ - void text_form(basis::astring &to_stuff, int how = MERIDIAN) const; - //!< Prints the time into "to_stuff" given "how". - /*!< note that "to_stuff" will be appended to; the existing contents - are retained. */ - - static int normalize(clock_time &to_fix); - // ensures that the units in each field are in the proper range by - // promoting them upwards. if the clock_time goes above the maximum hour, - // then it rolls around. zero is returned for no rollover or a positive - // integer is returned for the number of rollovers that occurred. if - // there are any negative fields, they are rolled backwards. - // the returned rollovers are measured in days. - }; - - //! An object that represents a particular day in a year. - class day_in_year : public virtual basis::packable - { - public: - months month; //!< The current month. - int day_in_month; //!< The day number within the month (starting at one). - days day_of_week; //!< The day of the week. - int day_of_year; //!< Numerical day, where January 1st is equal to zero. - - int packed_size() const { return 4 * structures::PACKED_SIZE_INT32; } - - //! Constructs a representation of the day specified. - day_in_year(months m = JANUARY, int dim = 1, days dow = SUNDAY, - int day_o_year = 1) : month(m), day_in_month(dim), - day_of_week(dow), day_of_year(day_o_year) {} - - virtual void pack(basis::byte_array &packed_form) const; - //!< Packs a day object into an array of bytes. - virtual bool unpack(basis::byte_array &packed_form); - //!< Unpacks a day object from an array of bytes. - - bool operator < (const day_in_year &to_compare) const; - //!< Returns true if this day is earlier than "to_compare" - /*!< Note that this only compares the month and day in the month. */ - bool operator == (const day_in_year &to_compare) const; - //!< Returns true if this day is equal to "to_compare" - /*!< Note that this only compares the month and day in the month. */ - - //! An enumeration of ways to print out the current date. - enum date_formats { - // note: these classes may need to be revised in the year 9999. - SHORT_MONTH = 0x1, //!< default: three letter month. - LONG_MONTH = 0x2, //!< uses full month name. - INCLUDE_DAY = 0x4 //!< adds the name of the day. - }; - - basis::astring text_form(int how = SHORT_MONTH) const; - //!< Prints the day according to "how". - void text_form(basis::astring &to_stuff, int how = SHORT_MONTH) const; - //!< Prints the day according to "how" and stores it in "to_stuff". - - static int normalize(day_in_year &to_fix, bool leap_year = false); - //!< normalizes the day as needed and returns the adjustment in years. - /*!< note that this only adjusts the day_in_month and month members - currently. the other counters are not changed. */ - }; - - //! An object that represents a particular point in time. - /*! It contains both a time of day and the day in the year. */ - class time_locus : public clock_time, public day_in_year, - public virtual basis::hoople_standard - { - public: - int year; //!< The year, using the gregorian calendar. - - time_locus() : clock_time(), day_in_year(), year() {} - - DEFINE_CLASS_NAME("time_locus"); - - //! Constructs a location in time given its components. - time_locus(const clock_time &ct, const day_in_year &ytd, int year_in) - : clock_time(ct), day_in_year(ytd), year(year_in) {} - - int packed_size() const { return clock_time::packed_size() - + day_in_year::packed_size() + structures::PACKED_SIZE_INT32; } - - virtual void pack(basis::byte_array &packed_form) const; - //!< Packs a time_locus object into an array of bytes. - virtual bool unpack(basis::byte_array &packed_form); - //!< Unpacks a time_locus object from an array of bytes. - - // these implement the orderable and equalizable interfaces. - virtual bool equal_to(const basis::equalizable &s2) const; - virtual bool less_than(const basis::orderable &s2) const; -//old bool operator < (const time_locus &to_compare) const; - //!< Returns true if this time_locus is earlier than "to_compare" -//old bool operator == (const time_locus &to_compare) const; - //!< Returns true if this time_locus is equal to "to_compare" - - //! Enumerates the ways to show the year. - enum locus_formats { - LONG_YEAR = 0x1, //!< default: full four digit year (problems in 9999). - SHORT_YEAR = 0x2 //!< use only last two digits of year. ugh--Y2K danger. - }; - - // fulfills obligation for text_formable. - virtual void text_form(basis::base_string &state_fill) const { - state_fill.assign(text_form_long(clock_time::MERIDIAN, day_in_year::SHORT_MONTH, LONG_YEAR)); - } - - basis::astring text_form_long(int t = clock_time::MERIDIAN, - int d = day_in_year::SHORT_MONTH, int y = LONG_YEAR) const; - //! Prints out the time_locus given the way to print each component. - /*< "t" is a combination of time_formats, "d" is a combination of - date_formats and "y" is a combination of locus_formats. */ - void text_form_long(basis::astring &to_stuff, int t = clock_time::MERIDIAN, - int d = day_in_year::SHORT_MONTH, int y = LONG_YEAR) const; - //! Same as text_form() above, but stores into "to_stuff". - - static int normalize(time_locus &to_fix); - //!< normalizes the time_locus for its clock time and date. -//hmmm: what are rollovers measured in? - }; - - int year_now(); //!< what year is it? - clock_time time_now(); //!< what time is it? - day_in_year date_now(); //!< what day on the calendar is it? - time_locus now(); //!< returns our current locus in the time continuum. - time_locus greenwich_now(); //!< returns Greenwich Mean Time (their now). -} // namespace. - -#endif - diff --git a/core/library/timely/makefile b/core/library/timely/makefile deleted file mode 100644 index c297d0f5..00000000 --- a/core/library/timely/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = timely -TYPE = library -SOURCE = earth_time.cpp stopwatch.cpp time_control.cpp time_stamp.cpp timer_driver.cpp -TARGETS = timely.lib - -include cpp/rules.def - diff --git a/core/library/timely/stopwatch.cpp b/core/library/timely/stopwatch.cpp deleted file mode 100644 index d5661cbc..00000000 --- a/core/library/timely/stopwatch.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/*********************** -* * -* Name : stopwatch -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "time_stamp.h" -#include "stopwatch.h" - -#include -#include -#include - -using namespace basis; - -namespace timely { - -stopwatch::stopwatch() -: _status(UNSTARTED), - _start_time(new time_stamp()), - _stop_time(new time_stamp()), - _total_so_far(0) -{} - -stopwatch::stopwatch(const stopwatch &to_copy) -: _status(UNSTARTED), - _start_time(new time_stamp()), - _stop_time(new time_stamp()), - _total_so_far(0) -{ *this = to_copy; } - -stopwatch::~stopwatch() -{ - _status = UNSTARTED; - WHACK(_start_time); - WHACK(_stop_time); -} - -stopwatch &stopwatch::operator =(const stopwatch &to_copy) -{ - if (this == &to_copy) return *this; - *_start_time = *to_copy._start_time; - *_stop_time = *to_copy._stop_time; - _status = to_copy._status; - _total_so_far = to_copy._total_so_far; - return *this; -} - -void stopwatch::reset() { _status = UNSTARTED; _total_so_far = 0; } - -int stopwatch::milliseconds() { return common_measure(); } - -void stopwatch::start() -{ - if (_status == RUNNING) return; - *_start_time = time_stamp(); - _status = RUNNING; -} - -int stopwatch::compute_diff(const time_stamp &t1, const time_stamp &t2) -{ return int(t2.value() - t1.value()); } - -void stopwatch::halt() -{ - if (_status == STOPPED) return; - else if (_status == UNSTARTED) return; - - *_stop_time = time_stamp(); - _total_so_far += compute_diff(*_start_time, *_stop_time); - - _status = STOPPED; -} - -int stopwatch::common_measure() -{ - bool restart = false; - int to_return = 0; - switch (_status) { - case UNSTARTED: break; - case RUNNING: - // stop stopwatch, restart afterwards. - halt(); - restart = true; - // intentional fall through to default. - default: - // set the return value to the accumulated time. - to_return = _total_so_far; - break; - } - if (restart) start(); // crank the stopwatch back up if we were supposed to. - return to_return; -} - -} //namespace. - diff --git a/core/library/timely/stopwatch.h b/core/library/timely/stopwatch.h deleted file mode 100644 index 964debf7..00000000 --- a/core/library/timely/stopwatch.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef STOPWATCH_CLASS -#define STOPWATCH_CLASS - -/*** -* -* Name : stopwatch -* Author : Chris Koeritz -******************************************************************************* -* Copyright (c) 1991-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "time_stamp.h" - -namespace timely { - -//! A class for measuring event durations in real time. -/*! - Once the stopwatch is constructed, it can then be repeatedly started and - halted, and then started again. The number of milliseconds or - microseconds elapsed can be requested while the stopwatch is running, but - that can disrupt fine-grained measurements. -*/ - -class stopwatch : public virtual basis::root_object -{ -public: - stopwatch(); - stopwatch(const stopwatch &to_copy); - - virtual ~stopwatch(); - - stopwatch &operator =(const stopwatch &to_copy); - - void start(); - //!< Begins the timing. - /*!< If the stopwatch is already timing, then "start" does nothing. */ - - void halt(); - //!< Stops the timing. - /*!< start() may be called again to resume timing after the halt. If the - stopwatch is already stopped, or never was started, then halt does nothing. */ - void stop() { halt(); } - //!< a synonym for halt(). - - void reset(); - //!< Stops the stopwatch and clears it to zero time elapsed. - - int milliseconds(); - //!< Returns the elapsed number of milliseconds on the stopwatch, overall. - int elapsed() { return milliseconds(); } - //!< a synonym for milliseconds(). - -private: - enum stopwatch_kinds { UNSTARTED, RUNNING, STOPPED }; //!< states for the stopwatch. - stopwatch_kinds _status; //!< our current state. - time_stamp *_start_time; //!< last time we got started. - time_stamp *_stop_time; //!< last time we got stopped. - int _total_so_far; //!< total amount of time run for so far. - - int common_measure(); - //!< returns the current time used to this point, in milliseconds. - - int compute_diff(const time_stamp &t1, const time_stamp &t2); - //!< the difference in milliseconds between the times "t1" and "t2". -}; - -////////////// - -//! Logs a warning when an operation takes longer than expected. -/*! - Place TIME_CHECK_BEGIN before the code that you want to check, then place - TIME_CHECK_END afterwards. The two calls must be in the same scope. - "logger" should be a reference to a log_base object. [ by Brit Minor ] -*/ -#define TIME_CHECK_BEGIN \ - stopwatch t; \ - t.start(); -#define TIME_CHECK_END(logger, who, msec_limit, what, filter) { \ - t.halt(); \ - if (t.milliseconds() > msec_limit) { \ - (logger).log( a_sprintf("TIME_CHECK: %s: %d ms wait for %s.", \ - (who), t.milliseconds(), (what)), filter); \ - } \ -} - -} //namespace. - -#endif - diff --git a/core/library/timely/time_control.cpp b/core/library/timely/time_control.cpp deleted file mode 100644 index 17e71ddb..00000000 --- a/core/library/timely/time_control.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Name : time_control -// Author : Chris Koeritz -/****************************************************************************** -* Copyright (c) 1994-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "time_control.h" - -#include -#include - -#include -#if defined(__WIN32__) || defined(__UNIX__) - #include -#endif -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace structures; - -namespace timely { - -void time_control::sleep_ms(basis::un_int msec) -{ -#ifdef __UNIX__ - usleep(msec * 1000); -#endif -#ifdef __WIN32__ - Sleep(msec); -#endif -} - -bool time_control::set_time(const time_locus &new_time) -{ -#ifdef __WIN32__ - SYSTEMTIME os_time; - os_time.wYear = WORD(new_time.year); - os_time.wMonth = new_time.month; - os_time.wDayOfWeek = new_time.day_of_week; - os_time.wDay = new_time.day_of_year; - os_time.wHour = new_time.hour; - os_time.wMinute = new_time.minute; - os_time.wSecond = new_time.second; - os_time.wMilliseconds = 0; // currently unused. - - // get our process token for manipulation. - HANDLE petoken; - OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES - | TOKEN_QUERY, &petoken); - // get our -//something or other -// identifier so we can adjust our privileges. - LUID our_id; - LookupPrivilegeValue(NULL, to_unicode_temp("SeSystemTimePrivilege"), &our_id); - // make up a privilege structure for the adjustment. - TOKEN_PRIVILEGES privs; - privs.PrivilegeCount = 1; - privs.Privileges[0].Luid = our_id; - privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - // enable system-time privileges. - AdjustTokenPrivileges(petoken, false, &privs, sizeof(TOKEN_PRIVILEGES), - NULL, NULL); - - SetLocalTime(&os_time); // actually set the time. - - // disable the time adjustment privileges again. - AdjustTokenPrivileges(petoken, true, &privs, sizeof(TOKEN_PRIVILEGES), - NULL, NULL); - - // let all the main windows know that the time got adjusted. -//do we need to do this ourselves? - ::PostMessage(HWND_BROADCAST, WM_TIMECHANGE, 0, 0); - -//hmmm: make sure this seems right. - CloseHandle(petoken); - - return true; -#elif defined(__UNIX__) -//no implem yet. - -//temp to shut up warnings -time_locus ted = new_time; -return ted.year ? 0:1; - -#else - return false; -#endif -} - -} // namespace. - diff --git a/core/library/timely/time_control.h b/core/library/timely/time_control.h deleted file mode 100644 index d2391b41..00000000 --- a/core/library/timely/time_control.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TIME_CONTROL_CLASS -#define TIME_CONTROL_CLASS - -/*****************************************************************************\ -* * -* Name : time_control -* Author : Chris Koeritz -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "earth_time.h" - -///#include -///#include -///#include - -namespace timely { - -//! Provides some functions that affect time, or ones perception of time. - -class time_control : public virtual basis::nameable -{ -public: - ~time_control() {} - - static void sleep_ms(basis::un_int msec); - //!< a system independent name for a forced snooze measured in milliseconds. - /*!< the application will do nothing on the current thread for the amount of - time specified. on some systems, if "msec" is zero, then the sleep will - yield the current thread's remaining timeslice back to other threads. */ - - bool set_time(const time_locus &new_time); - //!< makes the current time equal to "new_time". - /*!< This will only work if the operation is supported on this OS and if - the current user has the proper privileges. */ -}; - -} //namespace. - -#endif - diff --git a/core/library/timely/time_stamp.cpp b/core/library/timely/time_stamp.cpp deleted file mode 100644 index d015a342..00000000 --- a/core/library/timely/time_stamp.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/*****************************************************************************\ -* * -* Name : time_stamp * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "earth_time.h" -#include "time_stamp.h" - -#include -#include - -#include -#ifdef __WIN32__ - #define _WINSOCKAPI_ // make windows.h happy about winsock. -// #include - #include // timeval. -#endif - -using namespace basis; - -namespace timely { - -static mutex &__uptime_synchronizer() { - static mutex uptiming_syncher; - return uptiming_syncher; -} - -basis::astring time_stamp::notarize(bool add_space) -{ - const time_locus the_time = now(); - astring to_return; - the_time.text_form_long(to_return, clock_time::MILITARY | clock_time::MILLISECONDS); - if (add_space) to_return += " "; - return to_return; -} - -time_stamp::time_stamp() : c_stamp(0) { fill_in_time(); } - -time_stamp::time_stamp(time_representation offset) -: c_stamp(0) { reset(offset); } - -void time_stamp::reset() { fill_in_time(); } - -astring time_stamp::text_form(stamp_display_style style) const -{ - time_representation stump = c_stamp; - bool past = false; - if (style == STAMP_RELATIVE) { - // adjust the returned time by subtracting the current time. - stump -= get_time_now(); - if (negative(stump)) { - // if we're negative, just note that the stamp is in the past. - past = true; - stump = absolute_value(stump); - } - } - time_representation divisor = 3600 * SECOND_ms; - basis::un_int hours = basis::un_int(stump / divisor); - stump -= divisor * time_representation(hours); - divisor /= 60; - basis::un_int minutes = basis::un_int(stump / divisor); - stump -= divisor * time_representation(minutes); - divisor /= 60; - basis::un_int seconds = basis::un_int(stump / divisor); - stump -= divisor * time_representation(seconds); - basis::un_int milliseconds = basis::un_int(stump); - // make absolutely sure we are between 0 and 999. - milliseconds %= 1000; - - astring to_return; - bool did_hours = false; - if (hours) { - to_return += astring(astring::SPRINTF, "%uh:", hours); - did_hours = true; - } - if (minutes || did_hours) - to_return += astring(astring::SPRINTF, "%02um:", minutes); - to_return += astring(astring::SPRINTF, "%02us.%03u", seconds, milliseconds); - if (style == STAMP_RELATIVE) { - if (past) to_return += " ago"; - else to_return += " from now"; - } - return to_return; -} - -void time_stamp::fill_in_time() -{ - time_representation current = get_time_now(); - c_stamp = current; // reset our own time now. -} - -void time_stamp::reset(time_representation offset) -{ - fill_in_time(); - c_stamp += offset; -} - -time_stamp::time_representation time_stamp::get_time_now() -{ return rolling_uptime(); } - -const double __rollover_point = 2.0 * MAXINT32; - // this number is our rollover point for 32 bit integers. - -double time_stamp::rolling_uptime() -{ - auto_synchronizer l(__uptime_synchronizer()); - // protect our rollover records. - - static basis::un_int __last_ticks = 0; - static int __rollovers = 0; - - basis::un_int ticks_up = environment::system_uptime(); - // acquire the current uptime as a 32 bit unsigned int. - - if (ticks_up < __last_ticks) { - // rollover happened. increment our tracker. - __rollovers++; - } - __last_ticks = ticks_up; - - return double(__rollovers) * __rollover_point + double(ticks_up); -} - -timeval time_stamp::fill_timeval_ms(int duration) -{ - timeval time_out; // timeval has tv_sec=seconds, tv_usec=microseconds. - if (!duration) { - // duration is immediate for the check; just a quick poll. - time_out.tv_sec = 0; - time_out.tv_usec = 0; -#ifdef DEBUG_PORTABLE -// LOG("no duration specified"); -#endif - } else { - // a non-zero duration means we need to compute secs and usecs. - time_out.tv_sec = duration / 1000; - // set the number of seconds from the input in milliseconds. - duration -= time_out.tv_sec * 1000; - // now take out the chunk we've already recorded as seconds. - time_out.tv_usec = duration * 1000; - // set the number of microseconds from the remaining milliseconds. -#ifdef DEBUG_PORTABLE -// LOG(isprintf("duration of %d ms went to %d sec and %d usec.", duration, -// time_out.tv_sec, time_out.tv_usec)); -#endif - } - return time_out; -} - -} //namespace. - diff --git a/core/library/timely/time_stamp.h b/core/library/timely/time_stamp.h deleted file mode 100644 index 691660d8..00000000 --- a/core/library/timely/time_stamp.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef TIME_STAMP_CLASS -#define TIME_STAMP_CLASS - -/*****************************************************************************\ -* * -* Name : time_stamp * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -// forward. -class rollover_record; -struct timeval; - -namespace timely { - -//! Represents a point in time relative to the operating system startup time. -/*! - This duration is measured in milliseconds. This class provides a handy way - of measuring relative durations at the millisecond time scale. - Unfortunately, operating systems that reckon their millisecond uptime in - 32 bit integers will suffer from a rollover problem every 49 days or so, - but this class corrects this issue. -*/ - -class time_stamp : public virtual basis::orderable -{ -public: - typedef double time_representation; - //!< the representation of time for this universe, measured in milliseconds. - - time_stamp(); - //!< creates a time_stamp containing the current time. - - time_stamp(time_representation offset); - //!< creates a stamp after the current time by "offset" milliseconds. - /*!< negative offsets are allowed, in which case the stamp will be - prior to the current time. */ - - static double rolling_uptime(); - //!< give the OS uptime in a more durable form that handles rollovers. - - void reset(); - //!< sets the stamp time back to now. - void reset(time_representation offset); - //!< sets the stamp forward by "offset" similar to the second constructor. - - time_representation value() const { return c_stamp; } - //!< returns the time_stamp in terms of the lower level type. - - enum stamp_display_style { STAMP_RELATIVE, STAMP_ABSOLUTE }; - - basis::astring text_form(stamp_display_style style = STAMP_RELATIVE) const; - //!< returns a simple textual representation of the time_stamp. - /*!< if the "style" is ABSOLUTE, then the stamp is shown in H:M:S.ms - form based on the system uptime. if the "style" is RELATIVE, then the - stamp is shown as an offset from now. */ - - static basis::astring notarize(bool add_space = true); - //!< a useful method for getting a textual version of the time "right now". - /*!< this was formerly known as a very useful method called 'utility::timestamp'. - that naming was fairly abusive to keep straight from the time_stamp class, so the - functionality has been absorbed. */ - - // standard operators: keep in mind that time is represented by an ever - // increasing number. so, if a value A is less than a value B, that means - // that A is older than B, since it occurred at an earlier time. - virtual bool less_than(const basis::orderable &that) const { - const time_stamp *cast = dynamic_cast(&that); - if (!cast) return false; - return c_stamp < cast->c_stamp; - } - - virtual bool equal_to(const basis::equalizable &that) const { - const time_stamp *cast = dynamic_cast(&that); - if (!cast) return false; - return c_stamp == cast->c_stamp; - } - - // helper functions for using the OS's time support. - - static timeval fill_timeval_ms(int milliseconds); - //!< returns a timeval system object that represents the "milliseconds". - /*!< if "milliseconds" is zero, then the returned timeval will - represent zero time passing (rather than infinite duration as some - functions assume). */ - -private: - time_representation c_stamp; //!< the low-level time stamp is held here. - - void fill_in_time(); //!< stuffs the current time into the held value. - - static time_representation get_time_now(); - //!< platform specific function that gets uptime. - - static rollover_record &rollover_rover(); -}; - -} //namespace. - -#endif - diff --git a/core/library/timely/timer_driver.cpp b/core/library/timely/timer_driver.cpp deleted file mode 100644 index 9be8aba0..00000000 --- a/core/library/timely/timer_driver.cpp +++ /dev/null @@ -1,453 +0,0 @@ -/* -* Name : timer_driver -* Author : Chris Koeritz - -* Copyright (c) 2003-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "timer_driver.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#ifdef __UNIX__ - #include -#endif - -using namespace basis; -using namespace processes; -using namespace structures; -using namespace timely; - -//#define DEBUG_TIMER_DRIVER - // uncomment for noisy code. - -#undef LOG -#define LOG(tpr) printf( (time_stamp::notarize() + "timer_driver::" + func + tpr).s() ) - -namespace timely { - -const int INITIAL_TIMER_GRANULARITY = 14; - // the timer will support durations of this length or greater initially. - // later durations will be computed based on the timers waiting. - -const int MAX_TIMER_PREDICTION = 140; - // this is the maximum predictive delay before we wake up again to see if - // any new timed items have arrived. this helps us to not wait too long - // when something's scheduled in between snoozes. - -const int PAUSE_TIME = 200; - // we will pause this many milliseconds if the timer is already occurring - // when we're trying to get the lock on our list. - -const int LONG_TIME = 1 * HOUR_ms; - // the hook can be postponed a really long time with this when necessary. - -////////////// - -SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, ) - -////////////// - -#ifdef __UNIX__ -const int OUR_SIGNAL = SIGUSR2; - -class signalling_thread : public ethread -{ -public: - signalling_thread(int initial_interval) : ethread(initial_interval) {} - - void perform_activity(void *formal(ptr)) { - raise(OUR_SIGNAL); - } - -private: -}; -#endif - -#ifdef __UNIX__ -void timer_driver_private_handler(int signal_seen) -#elif defined(__WIN32__) -void __stdcall timer_driver_private_handler(window_handle hwnd, basis::un_int msg, - UINT_PTR id, un_long time) -#else - #error No timer method known for this OS. -#endif -{ -#ifdef DEBUG_TIMER_DRIVER - #undef static_class_name - #define static_class_name() "timer_driver" - FUNCDEF("timer_driver_private_handler"); -#endif -#ifdef __UNIX__ - int seen = signal_seen; - if (seen != OUR_SIGNAL) { -#elif defined(__WIN32__) - basis::un_int *seen = (basis::un_int *)id; - if (seen != program_wide_timer().real_timer_id()) { -#else - if (true) { // unknown OS. -#endif -#ifdef DEBUG_TIMER_DRIVER - LOG(a_sprintf("unknown signal/message %x caught.", (void *)seen)); -#endif - return; - } - program_wide_timer().handle_system_timer(); - #undef static_class_name -} - -////////////// - -class driven_object_record -{ -public: - int _duration; // the interval for timer hits on this object. - timeable *_to_invoke; // the object that will be called back. - time_stamp _next_hit; // next time the timer should hit for this object. - bool _okay_to_invoke; // true if this object is okay to call timers on. - bool _handling_timer; // true if we're handling this object right now. - - driven_object_record(int duration, timeable *to_invoke) - : _duration(duration), _to_invoke(to_invoke), _next_hit(duration), - _okay_to_invoke(true), _handling_timer(false) {} -}; - -class driven_objects_list -: public amorph, - public virtual root_object -{ -public: - DEFINE_CLASS_NAME("driven_objects_list"); - - int find_obj(timeable *obj) { - for (int i = 0; i < elements(); i++) { - if (borrow(i) && (borrow(i)->_to_invoke == obj)) - return i; - } - return common::NOT_FOUND; - } -}; - -////////////// - -timer_driver::timer_driver() -: _timers(new driven_objects_list), - _lock(new mutex), -#ifdef __UNIX__ - _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)), -#endif -#ifdef __WIN32__ - _real_timer_id(NIL), -#endif - _in_timer(false) -{ - hookup_OS_timer(INITIAL_TIMER_GRANULARITY); - -#ifdef __UNIX__ - // register for the our personal signal. - signal(OUR_SIGNAL, &timer_driver_private_handler); - _prompter->start(NIL); -#endif -} - -timer_driver::~timer_driver() -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("destructor"); -#endif -#ifdef __UNIX__ - _prompter->stop(); - - struct sigaction action; - action.sa_handler = SIG_DFL; - action.sa_sigaction = NIL; - sigemptyset(&action.sa_mask); - action.sa_flags = 0; -#ifndef __APPLE__ - action.sa_restorer = NIL; -#endif - int ret = sigaction(OUR_SIGNAL, &action, NIL); - if (ret) { -///uhhh - } -#endif - unhook_OS_timer(); - - // make sure we aren't still in a timer handler when we reset our list. - while (true) { - _lock->lock(); - if (_in_timer) { - _lock->unlock(); -#ifdef DEBUG_TIMER_DRIVER - LOG("waiting to acquire timer_driver lock."); -#endif - time_control::sleep_ms(PAUSE_TIME); - } else { - break; - } - } - - _timers->reset(); // clear out the registered functions. - _lock->unlock(); - - WHACK(_timers); - WHACK(_lock); -#ifdef __UNIX__ - WHACK(_prompter); -#endif - -#ifdef DEBUG_TIMER_DRIVER - LOG("timer_driver is closing down."); -#endif -} - -#ifdef __WIN32__ -basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; } -#endif - -bool timer_driver::zap_timer(timeable *to_remove) -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("zap_timer"); -#endif -#ifdef DEBUG_TIMER_DRIVER - if (_in_timer) { - LOG("hmmm: zapping timer while handling previous timer...!"); - } -#endif - auto_synchronizer l(*_lock); - int indy = _timers->find_obj(to_remove); - if (negative(indy)) return false; // unknown. -#ifdef DEBUG_TIMER_DRIVER - LOG(a_sprintf("zapping timer %x.", to_remove)); -#endif - driven_object_record *reco = _timers->borrow(indy); - reco->_okay_to_invoke = false; - if (reco->_handling_timer) { - // results are not guaranteed if we see this situation. -#ifdef DEBUG_TIMER_DRIVER - LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!", - to_remove)); -#endif - } - return true; -} - -bool timer_driver::set_timer(int duration, timeable *to_invoke) -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("set_timer"); - if (_in_timer) { - LOG("hmmm: setting timer while handling previous timer...!"); - } -#endif -#ifdef DEBUG_TIMER_DRIVER - LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration)); -#endif - auto_synchronizer l(*_lock); - // find any existing record. - int indy = _timers->find_obj(to_invoke); - if (negative(indy)) { - // add a new record to list. - _timers->append(new driven_object_record(duration, to_invoke)); - } else { - // change the existing record. - driven_object_record *reco = _timers->borrow(indy); - reco->_duration = duration; - reco->_okay_to_invoke = true; // just in case. - } - return true; -} - -void timer_driver::handle_system_timer() -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("handle_system_timer"); -#endif - if (_in_timer) { -#ifdef DEBUG_TIMER_DRIVER - LOG("terrible error: invoked system timer while handling previous timer."); -#endif - return; - } - unhook_OS_timer(); - -#ifdef DEBUG_TIMER_DRIVER - LOG("into handling OS timer..."); -#endif - - array to_invoke_now; - - { - // lock the list for a short time, just to put in a stake for the timer - // flag; no one is allowed to change the list while this is set to true. - auto_synchronizer l(*_lock); - _in_timer = true; - - // zip across our list and find out which of the timer functions should be - // invoked. - for (int i = 0; i < _timers->elements(); i++) { - driven_object_record *funky = _timers->borrow(i); - if (!funky) { - const char *msg = "error: timer_driver's timer list logic is broken."; -#ifdef DEBUG_TIMER_DRIVER - LOG(msg); -#endif -#ifdef CATCH_ERRORS - throw msg; -#endif - _timers->zap(i, i); - i--; // skip back over dud record. - continue; - } - if (funky->_next_hit <= time_stamp()) { - // this one needs to be jangled. - to_invoke_now += funky; - } - } - } - -#ifdef DEBUG_TIMER_DRIVER - astring pointer_dump; - for (int i = 0; i < to_invoke_now.length(); i++) { - driven_object_record *funky = to_invoke_now[i]; - pointer_dump += a_sprintf("%x ", funky->_to_invoke); - } - if (pointer_dump.t()) - LOG(astring("activating ") + pointer_dump); -#endif - - // now that we have a list of timer functions, let's call on them. - for (int i = 0; i < to_invoke_now.length(); i++) { - driven_object_record *funky = to_invoke_now[i]; - { - auto_synchronizer l(*_lock); - if (!funky->_okay_to_invoke) continue; // skip this guy. - funky->_handling_timer = true; - } - // call the timer function. - funky->_to_invoke->handle_timer_callback(); - { - auto_synchronizer l(*_lock); - funky->_handling_timer = false; - } - // reset the time for the next hit. - funky->_next_hit.reset(funky->_duration); - } - - // compute the smallest duration before the next guy should fire. - int next_timer_duration = MAX_TIMER_PREDICTION; - time_stamp now; // pick a point in time as reference for all timers. - for (int i = 0; i < _timers->elements(); i++) { - driven_object_record *funky = _timers->borrow(i); - int funky_time = int(funky->_next_hit.value() - now.value()); - // we limit the granularity of timing since we don't want to be raging - // on the CPU with too small a duration. - if (funky_time < INITIAL_TIMER_GRANULARITY) - funky_time = INITIAL_TIMER_GRANULARITY; - if (funky_time < next_timer_duration) - next_timer_duration = funky_time; - } - - { - // release the timer flag again and do any cleanups that are necessary. - auto_synchronizer l(*_lock); - _in_timer = false; - for (int i = 0; i < _timers->elements(); i++) { - driven_object_record *funky = _timers->borrow(i); - if (!funky->_okay_to_invoke) { - // clean up something that was unhooked. - _timers->zap(i, i); - i--; - } - } - } - -#ifdef DEBUG_TIMER_DRIVER - LOG("done handling OS timer."); -#endif - - // set the next expiration time to the smallest next guy. - reset_OS_timer(next_timer_duration); -} - -// the following OS_timer methods do not need to lock the mutex, since they -// are not actually touching the list of timers. - -void timer_driver::hookup_OS_timer(int duration) -{ - FUNCDEF("hookup_OS_timer"); - if (negative(duration)) { -#ifdef DEBUG_TIMER_DRIVER - LOG("seeing negative duration for timer!"); -#endif - duration = 1; - } else if (!duration) { -#ifdef DEBUG_TIMER_DRIVER - LOG("patching zero duration for timer."); -#endif - duration = 1; - } -#ifdef DEBUG_TIMER_DRIVER - LOG(a_sprintf("hooking next OS timer in %d ms.", duration)); -#endif -#ifdef __UNIX__ - // just make our thread hit after the duration specified. - _prompter->reschedule(duration); -#elif defined(__WIN32__) - int max_tries_left = 100; - while (max_tries_left-- >= 0) { - _real_timer_id = (basis::un_int *)SetTimer(NIL, 0, duration, - timer_driver_private_handler); - if (!_real_timer_id) { - // failure to set the timer. - LOG("could not set the interval timer."); - time_control::sleep_ms(50); // snooze for a bit to see if we can get right. - continue; - } else - break; // success hooking timer. - } -#endif -} - -void timer_driver::unhook_OS_timer() -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("unhook_OS_timer"); -#endif -#ifdef __UNIX__ - // postpone the thread for quite a while so we can take care of business. - _prompter->reschedule(LONG_TIME); -#elif defined(__WIN32__) - if (_real_timer_id) KillTimer(NIL, (UINT_PTR)_real_timer_id); -#endif -#ifdef DEBUG_TIMER_DRIVER - LOG("unhooked OS timer."); -#endif -} - -void timer_driver::reset_OS_timer(int next_hit) -{ -#ifdef DEBUG_TIMER_DRIVER - FUNCDEF("reset_OS_timer"); -#endif - unhook_OS_timer(); // stop the timer from running. - hookup_OS_timer(next_hit); // restart the timer with the new interval. -} - -} //namespace. - diff --git a/core/library/timely/timer_driver.h b/core/library/timely/timer_driver.h deleted file mode 100644 index ae2b9735..00000000 --- a/core/library/timely/timer_driver.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef TIMER_DRIVER_CLASS -#define TIMER_DRIVER_CLASS - -/*****************************************************************************\ -* * -* Name : timer_driver * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2003-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace timely { - -// forward. -class driven_objects_list; -class signalling_thread; - -////////////// - -//! timeable is the base for objects that can be hooked into timer events. - -class timeable : public virtual basis::root_object -{ -public: -//// virtual ~timeable() {} - virtual void handle_timer_callback() = 0; - //!< this method is invoked when the timer period elapses for this object. -}; - -////////////// - -//! Provides platform-independent timer support. -/*! - Multiple objects can be hooked to the timer to be called when their interval - elapses. The driver allows new timeables to be added as needed. - - NOTE: Only one of the timer_driver objects is allowed per program. -*/ - -class timer_driver : public virtual basis::root_object -{ -public: - timer_driver(); - virtual ~timer_driver(); - - DEFINE_CLASS_NAME("timer_driver"); - - // main methods for controlling timeables. - - bool set_timer(int duration, timeable *to_invoke); - //!< sets a timer to call "to_invoke" every "duration" milliseconds. - /*!< if the object "to_invoke" already exists, then its duration is - changed. */ - - bool zap_timer(timeable *to_drop); - //!< removes the timer that was established for "to_drop". - /*!< do not zap a timer from its own callback! that could cause - synchronization problems. */ - - // internal methods. - -#ifdef __WIN32__ - basis::un_int *real_timer_id(); - //!< provides the timer id for comparison on windows platforms. -#endif - - void handle_system_timer(); - //!< invoked by the OS timer support and must be called by main thread. - - static timer_driver &global_timer_driver(); - //!< the first time this is invoked, it creates a program-wide timer driver. - -private: - driven_objects_list *_timers; //!< timer hooked objects. - basis::mutex *_lock; //!< protects list of timers. -#ifdef __UNIX__ - signalling_thread *_prompter; //!< drives our timers. -#endif -#ifdef __WIN32__ - basis::un_int *_real_timer_id; //!< used for storing window timer handle. -#endif - bool _in_timer; //!< true if we're handling the timer right now. - - // these do the low-level system magic required to get a function hooked - // to a timer. - void hookup_OS_timer(int duration); - //!< hooks us into the timer events at the "duration" specified. - void reset_OS_timer(int next_hit); - //!< changes the root interval to "next_hit". - /*!< only that many milliseconds will elapse before the next timer hit. */ - void unhook_OS_timer(); - //!< disconnects us from the timer events. -}; - -////////////// - -#define program_wide_timer() timer_driver::global_timer_driver() - //!< provides access to the singleton timer_driver. - /*!< no other timer_driver objects should ever be created, since this - single one will service all timer needs within the program. */ - -} //namespace. - -#endif - diff --git a/core/library/unit_test/makefile b/core/library/unit_test/makefile deleted file mode 100644 index 7529795f..00000000 --- a/core/library/unit_test/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = unit_test -TYPE = library -SOURCE = unit_base.cpp -TARGETS = unit_test.lib - -include cpp/rules.def - diff --git a/core/library/unit_test/unit_base.cpp b/core/library/unit_test/unit_base.cpp deleted file mode 100644 index 439cc697..00000000 --- a/core/library/unit_test/unit_base.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/* -* Name : unit test tools -* Author : Chris Koeritz -** -* Copyright (c) 2009-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -#include "unit_base.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; - -#define BASE_LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) - -namespace unit_test { - -const int EXPECTED_MAXIMUM_TESTS = 10008; //!< maximum number of tests expected. - -const char *name_for_bools(bool bv) -{ if (bv) return "true"; else return "false"; } - -unit_base::unit_base() -: c_lock(), - c_total_tests(0), - c_passed_tests(0), - c_successful(EXPECTED_MAXIMUM_TESTS), - c_failed(EXPECTED_MAXIMUM_TESTS) -{ -} - -unit_base::~unit_base() {} - -int unit_base::total_tests() const { return c_total_tests; } - -int unit_base::passed_tests() const { return c_passed_tests; } - -int unit_base::failed_tests() const -{ return c_total_tests - c_passed_tests; } - -void unit_base::count_successful_test(const basis::astring &class_name, const basis::astring &test_name) -{ - auto_synchronizer synch(c_lock); - outcome ret = c_successful.add(class_name + " -- " + test_name, ""); - if (ret == common::IS_NEW) { - c_total_tests++; - c_passed_tests++; - } -} - -void unit_base::record_pass(const basis::astring &class_name, const astring &test_name, - const astring &diag) -{ - auto_synchronizer synch(c_lock); - count_successful_test(class_name, test_name); -//hmmm: kind of lame bailout on printing this. -// it gets very very wordy if it's left in. -#ifdef DEBUG - astring message = astring("OKAY: ") + class_name + " in test [" + test_name + "]"; - BASE_LOG(message); -#endif -} - -void unit_base::count_failed_test(const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diag) -{ - auto_synchronizer synch(c_lock); - outcome ret = c_failed.add(class_name + " -- " + test_name, diag); - if (ret == common::IS_NEW) { - c_total_tests++; - } -} - -void unit_base::record_fail(const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - count_failed_test(class_name, test_name, diag); - astring message = astring("\nFAIL: ") + class_name + " in test [" + test_name + "]\n" + diag + "\n"; - BASE_LOG(message); -} - -void unit_base::record_successful_assertion(const basis::astring &class_name, const astring &test_name, - const astring &assertion_name) -{ - record_pass(class_name, test_name, - astring("no problem with: ") + assertion_name); -} - -void unit_base::record_failed_object_compare(const hoople_standard &a, const hoople_standard &b, - const basis::astring &class_name, const astring &test_name, const astring &assertion_name) -{ - astring a_state, b_state; - a.text_form(a_state); - b.text_form(b_state); - record_fail(class_name, test_name, - astring("Error in assertion ") + assertion_name + ":\n" - + "==============\n" - + a_state + "\n" - + "=== versus ===\n" - + b_state + "\n" - + "=============="); -} - -void unit_base::record_failed_int_compare(int a, int b, - const basis::astring &class_name, const astring &test_name, const astring &assertion_name) -{ - record_fail(class_name, test_name, - astring("Error in assertion ") + assertion_name - + a_sprintf(": inappropriate values: %d & %d", a, b)); -} - -void unit_base::record_failed_double_compare(double a, double b, - const basis::astring &class_name, const astring &test_name, const astring &assertion_name) -{ - record_fail(class_name, test_name, - astring("Error in assertion ") + assertion_name - + a_sprintf(": inappropriate values: %f & %f", a, b)); -} - -void unit_base::record_failed_pointer_compare(const void *a, const void *b, - const basis::astring &class_name, const astring &test_name, const astring &assertion_name) -{ - record_fail(class_name, test_name, - astring("Error in assertion ") + assertion_name - + a_sprintf(": inappropriate values: %p & %p", a, b)); -} - -void unit_base::record_failed_tf_assertion(bool result, - bool expected_result, const basis::astring &class_name, const astring &test_name, - const astring &assertion_name) -{ - record_fail(class_name, test_name, astring("Error in assertion ") + assertion_name - + ": expected " + name_for_bools(expected_result) - + " but found " + name_for_bools(result)); -} - -void unit_base::assert_equal(const hoople_standard &a, const hoople_standard &b, - const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_equal"); - bool are_equal = (a == b); - if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_object_compare(a, b, class_name, test_name, func); -} - -void unit_base::assert_not_equal(const hoople_standard &a, const hoople_standard &b, - const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_not_equal"); - bool are_equal = (a == b); - if (!are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_object_compare(a, b, class_name, test_name, func); -} - -const char *byte_array_phrase = "byte_array[%d]"; // for printing a text form of byte_array. - -void unit_base::assert_equal(const basis::byte_array &a, const basis::byte_array &b, - const basis::astring &class_name, const basis::astring &test_name, const astring &diag) -{ - FUNCDEF("assert_equal"); - bool are_equal = (a == b); - if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else { - astring a_s = a_sprintf(byte_array_phrase, a.length()); - astring b_s = a_sprintf(byte_array_phrase, b.length()); - record_failed_object_compare(a_s, b_s, class_name, test_name, func); - } -} - -void unit_base::assert_not_equal(const basis::byte_array &a, const basis::byte_array &b, - const basis::astring &class_name, const basis::astring &test_name, const astring &diag) -{ - FUNCDEF("assert_not_equal"); - bool are_equal = (a == b); - if (!are_equal) record_successful_assertion(class_name, test_name, func); - else { - astring a_s = a_sprintf(byte_array_phrase, a.length()); - astring b_s = a_sprintf(byte_array_phrase, b.length()); - record_failed_object_compare(a_s, b_s, class_name, test_name, func); - } -} - -void unit_base::assert_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_equal integer"); - bool are_equal = a == b; - if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_not_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_not_equal integer"); - bool are_inequal = a != b; - if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_equal double"); - bool are_equal = a == b; - if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_not_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_not_equal double"); - bool are_inequal = a != b; - if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - - -void unit_base::assert_equal(const void *a, const void *b, - const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_equal void pointer"); - bool are_equal = a == b; - if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_not_equal(const void *a, const void *b, - const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_not_equal void pointer"); - bool are_inequal = a != b; - if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_true(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_true"); - if (result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_tf_assertion(result, true, class_name, test_name, astring(func) + ": " + diag); -} - -void unit_base::assert_false(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag) -{ - FUNCDEF("assert_false"); - if (!result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); - else record_failed_tf_assertion(result, false, class_name, test_name, astring(func) + ": " + diag); -} - -int unit_base::final_report() -{ - auto_synchronizer synch(c_lock); - int to_return = 0; // return success until we know otherwise. - - astring keyword = "FAILURE"; // but be pessimistic about overall result at first..? - - // check whether we really did succeed or not. - if (c_total_tests == c_passed_tests) keyword = "SUCCESS"; // success! - else to_return = 12; // a failure return. - - if (!c_total_tests) keyword = "LAMENESS (no tests!)"; // boring! - -// astring message = keyword + " for " -// + application_configuration::application_name() -// + a_sprintf(": %d of %d atomic tests passed.", -// c_passed_tests, c_total_tests); -// BASE_LOG(message); - - astring message = keyword + " for " - + filename(application_configuration::application_name()).basename().raw() - + a_sprintf(": %d of %d unit tests passed.", - c_successful.symbols(), c_successful.symbols() + c_failed.symbols()); - BASE_LOG(message); - - // send an xml file out for the build engine to analyze. - write_cppunit_xml(); - - return to_return; -} - -void unit_base::write_cppunit_xml() -{ - auto_synchronizer synch(c_lock); - astring logs_dir = environment::get("LOGS_DIR"); - if (logs_dir == astring::empty_string()) logs_dir = "logs"; // uhhh. - astring outfile = logs_dir + "/" - + filename(application_configuration::application_name()).basename().raw() - + ".xml"; - -//BASE_LOG(astring("outfile is ") + outfile); - - int id = 1; - - xml_generator report; - string_table attribs; - - // we are emulating a cppunit xml output. - - report.open_tag("TestRun"); - - report.open_tag("FailedTests"); - for (int i = 0; i < c_failed.symbols(); i++) { - attribs.reset(); - attribs.add("id", a_sprintf("%d", id++)); - report.open_tag("FailedTest", attribs); - attribs.reset(); -//hmmm: does open_tag eat the attribs? we could stop worrying about resetting. - report.open_tag("Name"); - report.add_content(c_failed.name(i)); - report.close_tag("Name"); - - report.open_tag("FailureType", attribs); - report.add_content("Assertion"); - report.close_tag("FailureType"); - - report.open_tag("Location", attribs); - - report.open_tag("File", attribs); - report.add_content(application_configuration::application_name()); - report.close_tag("File"); - - report.open_tag("Line", attribs); - report.add_content("0"); - report.close_tag("Line"); - - report.close_tag("Location"); - - report.open_tag("Message"); - report.add_content(c_failed[i]); - report.close_tag("Message"); - - report.close_tag("FailedTest"); - } - report.close_tag("FailedTests"); - - report.open_tag("SuccessfulTests"); - for (int i = 0; i < c_successful.symbols(); i++) { - attribs.reset(); - attribs.add("id", a_sprintf("%d", id++)); - attribs.reset(); - report.open_tag("Test", attribs); - report.open_tag("Name"); - report.add_content(c_successful.name(i)); - report.close_tag("Name"); - report.close_tag("Test"); - } - report.close_tag("SuccessfulTests"); - - report.open_tag("Statistics"); - report.open_tag("Tests"); - report.add_content(a_sprintf("%d", c_failed.symbols() + c_successful.symbols())); - report.close_tag("Tests"); - - report.open_tag("FailuresTotal"); - report.add_content(a_sprintf("%d", c_failed.symbols())); - report.close_tag("FailuresTotal"); - - report.open_tag("Errors"); - report.add_content("0"); - report.close_tag("Errors"); - - report.open_tag("Failures"); - report.add_content(a_sprintf("%d", c_failed.symbols())); - report.close_tag("Failures"); - - report.close_tag("Statistics"); - - report.close_tag("TestRun"); - - astring text_report = report.generate(); -// BASE_LOG(astring("got report\n") + text_report); - - byte_filer xml_out(outfile, "wb"); - xml_out.write(text_report); - xml_out.close(); -} - -} //namespace. - diff --git a/core/library/unit_test/unit_base.h b/core/library/unit_test/unit_base.h deleted file mode 100644 index d5185dec..00000000 --- a/core/library/unit_test/unit_base.h +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef UNIT_BASE_GROUP -#define UNIT_BASE_GROUP - -/* -* Name : unit_base tools for unit testing -* Author : Chris Koeritz -** -* Copyright (c) 2009-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -*/ - -//! Useful support functions for unit testing, especially within hoople. -/*! - Provides a simple framework for unit testing the hoople classes. -*/ - -#include -#include -#include -#include -#include - -namespace unit_test { - -// these macros can be used to put more information into the test name. -// using these is preferable to calling the class methods directly, since -// those cannot print out the original version of the formal parameters a -// and b for the test; the macros show the expressions that failed rather -// than just the values. -// note that these require the calling object to be derived from unit_base. -#define UNIT_BASE_THIS_OBJECT (*this) - // the macro for UNIT_BASE_THIS_OBJECT allows tests to use a single unit_base object by - // changing the value of UNIT_BASE_THIS_OBJECT to refer to the proper object's name. -#define ASSERT_EQUAL(a, b, test_name) { \ - BASE_FUNCTION(func); \ - UNIT_BASE_THIS_OBJECT.assert_equal(a, b, function_name, test_name, basis::astring(#a) + " must be equal to " + #b); \ -} -#define ASSERT_INEQUAL(a, b, test_name) { \ - BASE_FUNCTION(func); \ - UNIT_BASE_THIS_OBJECT.assert_not_equal(a, b, function_name, test_name, basis::astring(#a) + " must be inequal to " + #b); \ -} -#define ASSERT_TRUE(a, test_name) { \ - BASE_FUNCTION(func); \ - UNIT_BASE_THIS_OBJECT.assert_true(a, function_name, test_name, basis::astring(#a) + " must be true"); \ -} -#define ASSERT_FALSE(a, test_name) { \ - BASE_FUNCTION(func); \ - UNIT_BASE_THIS_OBJECT.assert_false(a, function_name, test_name, basis::astring(#a) + " must be false"); \ -} -// pointer versions for nicer syntax. -#define ASSERT_NULL(x, y) ASSERT_FALSE(x, y) -#define ASSERT_NON_NULL(x, y) ASSERT_TRUE(x, y) - -class unit_base : public virtual basis::nameable -{ -public: - unit_base(); - virtual ~unit_base(); - - DEFINE_CLASS_NAME("unit_base"); - - int total_tests() const; //!< the total count of tests that have been run. - int passed_tests() const; //!< count of successful tests run. - int failed_tests() const; //!< count of number of failed tests. - - void assert_equal(const basis::hoople_standard &a, const basis::hoople_standard &b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that the objects a and b are equal. - void assert_not_equal(const basis::hoople_standard &a, const basis::hoople_standard &b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that objects a and b are NOT equal. - - void assert_equal(const basis::byte_array &a, const basis::byte_array &b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that the byte_arrays a and b are equal. - void assert_not_equal(const basis::byte_array &a, const basis::byte_array &b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that the byte_arrays a and b are not equal. - - void assert_equal(int a, int b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that integers a and b are equal. - void assert_not_equal(int a, int b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that integers a and b are NOT equal. - - void assert_equal(double a, double b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that doubles a and b are equal. - void assert_not_equal(double a, double b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that doubles a and b are NOT equal. - - void assert_equal(const void *a, const void *b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that void pointers a and b are equal. - /*!< note that you can use this to compare any two pointers as long as - you cast them to (void *) first. this reports whether the two have the - same address in memory, but nothing about their contents. */ - void assert_not_equal(const void *a, const void *b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that void pointers a and b are NOT equal. - - void assert_true(bool result, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that the "result" is a true boolean. - void assert_false(bool result, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< tests that the "result" is a false boolean. - - // these two methods can be used in an ad hoc manner when using the above - // assert methods is not helpful. the "test_name" should be provided - // like above, but the "diagnostic_info" can be phrased in any way needed - // to describe the pass or fail event. - void record_pass(const basis::astring &class_name, - const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< very general recording of a successful test; better to use asserts. - void record_fail(const basis::astring &class_name, - const basis::astring &test_name, - const basis::astring &diagnostic_info); - //!< very general recording of a failed test; better to use asserts. - - int final_report(); - //!< generates a report of the total number of tests that succeeded. - /*!< the return value of this can be used as the exit value of the overall test. if there - are any failures, then a failure result is returned (non-zero). otherwise the successful - exit status of zero is returned. */ - -private: - basis::mutex c_lock; //!< protects our objects for concurrent access. - int c_total_tests; //!< how many tests have been run? - int c_passed_tests; //!< how many of those passed? - structures::string_table c_successful; //!< successful test names. - structures::string_table c_failed; //!< failing test names. - - void write_cppunit_xml(); - //!< outputs a report file in cppunit format so CI engines can see results. - - void count_successful_test(const basis::astring &class_name, const basis::astring &test_name); - //!< records one successful test. - - void count_failed_test(const basis::astring &class_name, const basis::astring &test_name, const basis::astring &diag); - //!< records one failed test. - - void record_successful_assertion(const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &assertion_name); - //!< used by happy tests to record their success. - - void record_failed_object_compare(const basis::hoople_standard &a, - const basis::hoople_standard &b, const basis::astring &class_name, - const basis::astring &test_name, const basis::astring &assertion_name); - void record_failed_int_compare(int a, int b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &assertion_name); - void record_failed_double_compare(double a, double b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &assertion_name); - void record_failed_tf_assertion(bool result, bool expected_result, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &assertion_name); - void record_failed_pointer_compare(const void *a, const void *b, - const basis::astring &class_name, const basis::astring &test_name, - const basis::astring &assertion_name); -}; - -} //namespace. - -#endif - diff --git a/core/library/versions/makefile b/core/library/versions/makefile deleted file mode 100644 index 0eea688c..00000000 --- a/core/library/versions/makefile +++ /dev/null @@ -1,9 +0,0 @@ -include cpp/variables.def - -PROJECT = versions -TYPE = library -SOURCE = version_checker.cpp version_ini.cpp -TARGETS = versions.lib - -include cpp/rules.def - diff --git a/core/library/versions/version.ini.example b/core/library/versions/version.ini.example deleted file mode 100644 index 3fc5e56a..00000000 --- a/core/library/versions/version.ini.example +++ /dev/null @@ -1,19 +0,0 @@ -# This is an example "version.ini" file for a library or application. -# Using this file and the "main_ver.ini" (see "proto_main_ver.ini" for more -# information), the version support creates the version record in a resource -# file and outputs the header for version checking (libraries only). - -[version] - ; currently, the only section name used is "version". -description=Gurp Library: assorted multi-purpose frotzing components - ; longish text summary of the library's purpose. -name=Gurp_Library - ; the name that the library goes by. how it thinks of itself. -root=gurp - ; the root name for the project's dll or exe file. don't include the - ; extension; that's specified below. -extension=dll - ; type of file created by the project. the default is "dll", which indicates - ; that a version checking header should be created (called "version.h"). - ; the other option is "exe", which doesn't bother with the version header. - diff --git a/core/library/versions/version_checker.cpp b/core/library/versions/version_checker.cpp deleted file mode 100644 index 24d06b69..00000000 --- a/core/library/versions/version_checker.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/*****************************************************************************\ -* * -* Name : check_version * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "version_checker.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace basis; -using namespace configuration; -using namespace loggers; -using namespace structures; - -#ifndef BOOT_STRAPPING - // pull in the version specified for this build. -///hmmm: on hold! #include <__build_version.h> -#else - // plug in a fake version for our bootstrapping process. - #define __build_FILE_VERSION "108.420.1024.10008" -#endif - -#ifdef _MSC_VER - #include - #include -#endif - -#ifdef __WIN32__ - // ensures that we handle the data properly regardless of unicode settings. - #ifdef UNICODE - #define render_ptr(ptr) from_unicode_temp( (UTF16 *) ptr) - #else - #define render_ptr(ptr) ( (char *) ptr) - #endif -#endif - -////////////// - -namespace versions { - -version_checker::version_checker(const astring &library_file_name, - const version &expected_version, const astring &version_complaint) -: _library_file_name(new astring(library_file_name)), - _expected_version(new version(expected_version)), - _version_complaint(new astring(version_complaint)) -{} - -version_checker::~version_checker() -{ - WHACK(_library_file_name); - WHACK(_expected_version); - WHACK(_version_complaint); -} - -astring version_checker::text_form() const -{ - return astring(class_name()) + ": library_file_name=" + *_library_file_name - + ", expected_version=" + _expected_version->text_form() - + ", complaint_message=" + *_version_complaint; -} - -bool version_checker::good_version() const -{ - astring version_disabler = environment::get("TMP"); - version_disabler += "/no_version_check.txt"; - FILE *always_okay = fopen(version_disabler.s(), "r"); - if (always_okay) { - fclose(always_okay); - return true; - } - - version version_found = retrieve_version(*_library_file_name); - if (version_found.compatible(*_expected_version)) return true; // success. - complain_wrong_version(*_library_file_name, *_expected_version, - version_found); - return false; -} - -bool version_checker::loaded(const astring &library_file_name) -{ -#ifdef __WIN32__ - return bool(get_handle(library_file_name) != 0); -#else -//temp code. - return true || library_file_name.t(); -#endif -} - -void *version_checker::get_handle(const astring &library_file_name) -{ -#ifdef __WIN32__ - return GetModuleHandle(to_unicode_temp(library_file_name)); -#else -//hmmm: there really isn't this concept on OSes that i'm aware of. - return 0 && library_file_name.t(); -#endif -} - -astring version_checker::module_name(const void *module_handle) -{ -#ifdef __UNIX__ -//hmmm: implement module name for linux if that makes sense. - if (module_handle) {} - return application_configuration::application_name(); -#elif defined(__WIN32__) - flexichar low_buff[MAX_ABS_PATH + 1]; - GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1); - astring buff = from_unicode_temp(low_buff); - buff.to_lower(); - return buff; -#else - #pragma message("module_name unknown for this operating system.") - return application_name(); -#endif -} - -astring version_checker::get_name(const void *to_find) -{ return module_name(to_find); } - -bool version_checker::retrieve_version_info(const astring &filename, - byte_array &to_fill) -{ - to_fill.reset(); // clear the buffer. - - // determine the required size of the version info buffer. - int required_size; -#ifdef __WIN32__ - un_long module_handle; // filled with the dll or exe handle. - required_size = GetFileVersionInfoSize(to_unicode_temp(filename), &module_handle); -#else - required_size = 0 && filename.t(); -#endif - if (!required_size) return false; - to_fill.reset(required_size); // resize the buffer. - - // read the version info into our buffer. - bool success = false; -#ifdef __WIN32__ - success = GetFileVersionInfo(to_unicode_temp(filename), module_handle, - required_size, to_fill.access()); -#else - success = false; -#endif - return success; -} - -bool version_checker::get_language(byte_array &version_chunk, - basis::un_short &high, basis::un_short &low) -{ - high = 0; - low = 0; -#ifdef __WIN32__ - // determine the language that the version's written in. - basis::un_int data_size; - void *pointer_to_language_structure; - // query the information from the version blob. - if (!VerQueryValue(version_chunk.access(), - to_unicode_temp("\\VarFileInfo\\Translation"), - &pointer_to_language_structure, &data_size)) - return false; - // get the low & high shorts of the structure. - high = LOWORD(*(unsigned int *)pointer_to_language_structure); - low = HIWORD(*(unsigned int *)pointer_to_language_structure); -#else - high = 0 && version_chunk.length(); - low = 0; -#endif - - return true; -} - -version version_checker::retrieve_version(const astring &filename) -{ -#ifdef UNIX - - // totally bogus stand-in; this just returns the version we were built with - // rather than the version that's actually tagged on the file. - -//hmmm: fix this! get the version header back. -// return version(__build_FILE_VERSION); - return version(); - -#endif - - byte_array version_info_found(0, NIL); - if (!retrieve_version_info(filename, version_info_found)) - return version(0, 0, 0, 0); - - basis::un_short high, low; // holds the language of the version data. - if (!get_language(version_info_found, high, low)) - return version(0, 0, 0, 0); - - // retrieve the file version from version info using the appropriate - // language. - astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low); - astring file_version_key(root_key + astring("\\FileVersion")); - - astring version_string; -#ifdef __WIN32__ - abyte *file_version_pointer; - basis::un_int data_size; - if (!VerQueryValue(version_info_found.access(), - to_unicode_temp(file_version_key), - (LPVOID *)&file_version_pointer, &data_size)) - return version(0, 0, 0, 0); - version_string = render_ptr(file_version_pointer); - // clean any spaces out of the string; people sometimes format these - // very badly. - for (int i = 0; i < version_string.length(); i++) { - if (version_string[i] == ' ') { - version_string.zap(i, i); - i--; // skip back a beat. - } - } -#else - return version(0, 0, 0, 0); -//tmp. -#endif - return version::from_text(version_string); -} - -bool version_checker::get_record(const astring &filename, - version_record &to_fill) -{ - to_fill = version_record(); - byte_array version_info_found(0, NIL); - if (!retrieve_version_info(filename, version_info_found)) - return false; - - basis::un_short high, low; // holds the language of the version data. - if (!get_language(version_info_found, high, low)) - return false; - - // set the root key for all accesses of the version chunk. - astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low); - - // reports whether all lookups succeeded or not. - bool total_success = true; - - // the various version pieces are retrieved... - -#ifdef __WIN32__ - basis::un_int data_size; - void *data_pointer; - - // file version. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\FileVersion")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.file_version = version::from_text(render_ptr(data_pointer)); - - // company name. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\CompanyName")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.company_name = render_ptr(data_pointer); - - // file description. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\FileDescription")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.description = render_ptr(data_pointer); - - // internal name. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\InternalName")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.internal_name = render_ptr(data_pointer); - - // copyright info. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\LegalCopyright")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.copyright = render_ptr(data_pointer); - - // trademark info. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\LegalTrademarks")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.trademarks = render_ptr(data_pointer); - - // original file name. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\OriginalFilename")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.original_name = render_ptr(data_pointer); - - // product name. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\ProductName")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.product_name = render_ptr(data_pointer); - - // product version. - if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key - + astring("\\ProductVersion")), &data_pointer, &data_size)) - total_success = false; - else - to_fill.product_version = version::from_text(render_ptr(data_pointer)); -#else - // hmmm: chunks missing in version check. -#endif - - return total_success; -} - -void version_checker::complain_wrong_version(const astring &library_file_name, - const version &expected_version, const version &version_found) const -{ - astring to_show("There has been a Version Mismatch: The module \""); - // use embedded module handle to retrieve name of dll or exe. - astring module_name = get_name(version::__global_module_handle()); - if (!module_name) module_name = "Unknown"; - to_show += module_name; - to_show += astring("\" cannot load. This is because the file \""); - to_show += library_file_name; - to_show += astring("\" was expected to have a version of ["); - - to_show += expected_version.flex_text_form(version::DOTS); - - to_show += astring("] but it instead had a version of ["); - to_show += version_found.flex_text_form(version::DOTS); - - to_show += astring("]. "); - to_show += *_version_complaint; -#ifdef __UNIX__ - continuable_error("version checking", "failure", to_show.s()); -#elif defined(__WIN32__) - MessageBox(0, to_unicode_temp(to_show), - to_unicode_temp("version_checking::failure"), MB_OK); -#endif -} - -void version_checker::complain_cannot_load(const astring &lib_file) const -{ - astring to_show("There has been a failure in Version Checking: The file \""); - to_show += lib_file; - to_show += astring("\" could not be loaded or found. "); - to_show += *_version_complaint; - continuable_error("version checking", "loading dll", to_show.s()); -} - -} //namespace. - - diff --git a/core/library/versions/version_checker.h b/core/library/versions/version_checker.h deleted file mode 100644 index 9e810155..00000000 --- a/core/library/versions/version_checker.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef VERSION_CHECKER_CLASS -#define VERSION_CHECKER_CLASS - -/*****************************************************************************\ -* * -* Name : check_version * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1996-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -namespace versions { - -//! Provides version checking for shared libraries. - -class version_checker : public virtual basis::root_object -{ -public: - version_checker(const basis::astring &library_file_name, const structures::version &expected, - const basis::astring &version_complaint); - //!< Constructs a checking object and ensures the version is appropriate. - /*!< the version checking (a call to the good_version() function) will - succeed if the library with the "library_file_name" (such as - "basis32.dll") has the "expected" version. the simplest way to check - if the version is correct is probably similar to: @code - if (!version_checker("my.dll", version(1.2.3.4)).good_version()) { - ...program exit or version failure management... - } @endcode - the "version_complaint" is the message that will be displayed on a - failure in version checking (with noisy mode enabled). it should - describe that a version failure occurred and include contact information - for the customer to get the most recent versions. for example: @code - astring version_grievance = "Please contact Floobert Corporation for " - "the latest DLL and Executable files (http://www.floobert.com)."; - @endcode */ - - virtual ~version_checker(); //!< Destructor releases any resources. - - bool good_version() const; - //!< Performs the actual version check. - /*!< If the version check is unsuccessful, then a message that - describes the problem is shown to the user and false is returned. - NOTE: the version check will also fail if the version information - structure cannot be found for that library. */ - - static basis::astring module_name(const void *module_handle); - //!< returns the module name where this object resides; only sensible on win32. - - // base requirements. - DEFINE_CLASS_NAME("version_checker"); - basis::astring text_form() const; - - static bool loaded(const basis::astring &library_file_name); - //!< returns true if the "library_file_name" is currently loaded. - static void *get_handle(const basis::astring &library_file_name); - //!< retrieves the module handle for the "library_file_name". - /*!< This returns zero if the library cannot be located. the returned - pointer wraps a win32 HMODULE currently, or it is meaningless. */ - static basis::astring get_name(const void *to_find); - //!< returns the name of the HMODULE specified by "to_find". - /*!< If that handle cannot be located, then an empty string is returned. */ - - static structures::version retrieve_version(const basis::astring &pathname); - //!< Returns the version given a "pathname" to the DLL or EXE file. - /*!< If the directory component is not included, then the search path - will be used. */ - - static bool get_record(const basis::astring &pathname, structures::version_record &to_fill); - //!< Retrieves a version record for the file at "pathname". - /*!< Returns the full version record found for a given "pathname" to - the DLL or EXE file in the record "to_fill". if the directory component - of the path is not included, then the search path will be used. false is - returned if some piece of information could not be located; this does not - necessarily indicate a total failure of the retrieval. */ - - static bool retrieve_version_info(const basis::astring &filename, - basis::byte_array &to_fill); - //!< Retrieves the version info for the "filename" into the array "to_fill". - - static bool get_language(basis::byte_array &version_chunk, basis::un_short &high, - basis::un_short &low); - //!< Gets the language identifier out of the "version_chunk". - /*!< Returns true if the language identifier for the dll's version chunk - could be stored in "high" and "low". This is a win32-only method. */ - - void complain_wrong_version(const basis::astring &library_file_name, - const structures::version &expected_version, - const structures::version &version_found) const; - //!< Reports that the file has the wrong version. - - void complain_cannot_load(const basis::astring &library_file_name) const; - //!< Reports that the dll could not be loaded. - -private: - basis::astring *_library_file_name; - structures::version *_expected_version; - basis::astring *_version_complaint; - - // forbidden. - version_checker(const version_checker &); - version_checker &operator =(const version_checker &); -}; - -} //namespace. - -#endif - diff --git a/core/library/versions/version_ini.cpp b/core/library/versions/version_ini.cpp deleted file mode 100644 index 7edb6f1e..00000000 --- a/core/library/versions/version_ini.cpp +++ /dev/null @@ -1,599 +0,0 @@ -/*****************************************************************************\ -* * -* Name : version_ini editing support * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "version_ini.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef __WIN32__ - #include -#endif - -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; - -namespace versions { - -// the following are all strings that are sought in the version.ini file. -const char *version_ini::VERSION_SECTION = "version"; - // the section that version entries are stored under in the INI file. -const char *version_ini::COMPANY_KEY="company"; -const char *version_ini::COPYRIGHT_KEY="copyright"; -const char *version_ini::LEGAL_INFO_KEY="legal_info"; -const char *version_ini::PRODUCT_KEY="product_name"; -const char *version_ini::WEB_SITE_KEY="web_site"; - -// not used anymore; now matched with the file version's first two digits. -//const version PRODUCT_VERSION(2, 0, 0, 0); - // the current version of the entire product. - -// these are field names in the INI file. -const char *version_ini::MAJOR = "major"; -const char *version_ini::MINOR = "minor"; -const char *version_ini::REVISION = "revision"; -const char *version_ini::BUILD = "build"; -const char *version_ini::DESCRIPTION = "description"; -const char *version_ini::ROOT = "root"; -const char *version_ini::NAME = "name"; -const char *version_ini::EXTENSION = "extension"; -const char *version_ini::OLE_AUTO = "ole_auto"; - -// this is the default version INI file name, if no other is specified. -const char *VERSION_INI_FILE = "/version.ini"; - -#undef LOG -#define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t) - -version_ini::version_ini(const astring &path_name) -: _loaded(false), - _path_name(new filename(path_name)), - _ini(new ini_configurator("", ini_configurator::RETURN_ONLY)), - _held_record(new version_record) -{ - check_name(*_path_name); - _ini->name(*_path_name); -} - -version_ini::~version_ini() -{ - WHACK(_ini); - WHACK(_path_name); - WHACK(_held_record); -} - -bool version_ini::ole_auto_registering() -{ - astring extension = _ini->load(VERSION_SECTION, OLE_AUTO, ""); - return (extension.lower().t()); -} - -bool version_ini::executable() -{ - astring extension = _ini->load(VERSION_SECTION, EXTENSION, ""); - if (extension.lower() == astring("exe")) return true; - return false; -} - -bool version_ini::library() { return !executable(); } - -bool version_ini::writable() { return _path_name->is_writable(); } - -void version_ini::check_name(filename &to_examine) -{ - // if it's just a directory name, add the file name. - if (to_examine.is_directory()) { - to_examine = astring(to_examine) + VERSION_INI_FILE; - to_examine.canonicalize(); - } - - // add the directory explicitly (if it's not there already) or the ini - // writer will get it from the windows directory. - if ( (to_examine.raw()[0] != '.') && (to_examine.dirname().raw().equal_to(".")) ) { - to_examine = astring("./") + to_examine; - to_examine.canonicalize(); - } -} - -bool version_ini::executable(const astring &path_name_in) -{ - filename path_name(path_name_in); - check_name(path_name); - ini_configurator temp_ini(path_name, ini_configurator::RETURN_ONLY); - astring extension = temp_ini.load(VERSION_SECTION, EXTENSION, ""); - extension.to_lower(); - if (extension == astring("exe")) return true; - return false; -} - -bool version_ini::library(const astring &path_name) -{ return !executable(path_name); } - -version version_ini::get_version() -{ - if (_loaded) return _held_record->file_version; - get_record(); - return _held_record->file_version; -} - -void version_ini::set_version(const version &to_write, bool write_ini) -{ - _held_record->file_version = to_write; // copy the version we're given. - - // set the product version appropriately to the file version. - _held_record->product_version = to_write; - _held_record->product_version.set_component(version::REVISION, "0"); - _held_record->product_version.set_component(version::BUILD, "0"); - - if (!write_ini) return; // they don't want a change to the file. - _ini->store(VERSION_SECTION, MAJOR, to_write.get_component(version::MAJOR)); - _ini->store(VERSION_SECTION, MINOR, to_write.get_component(version::MINOR)); - _ini->store(VERSION_SECTION, REVISION, to_write.get_component(version::REVISION)); - _ini->store(VERSION_SECTION, BUILD, to_write.get_component(version::BUILD)); -} - -version version_ini::read_version_from_ini() -{ - string_array parts; - parts += _ini->load(VERSION_SECTION, MAJOR, "0"); - parts += _ini->load(VERSION_SECTION, MINOR, "0"); - parts += _ini->load(VERSION_SECTION, REVISION, "0"); - parts += _ini->load(VERSION_SECTION, BUILD, "0"); - return version(parts); -} - -version_record &version_ini::access_record() { return *_held_record; } - -version_record version_ini::get_record() -{ -// FUNCDEF("get_record"); - if (_loaded) return *_held_record; - version_record to_return; - to_return.description = _ini->load(VERSION_SECTION, DESCRIPTION, ""); - to_return.file_version = read_version_from_ini(); - to_return.internal_name = _ini->load(VERSION_SECTION, NAME, ""); - to_return.original_name = _ini->load(VERSION_SECTION, ROOT, ""); - to_return.original_name += "."; - - // the dll type of extension is a hard default. anything besides the exe - // ending gets mapped to dll. - astring extension = _ini->load(VERSION_SECTION, EXTENSION, ""); - extension.to_lower(); - if (extension.equal_to("dll")) {} - else if (extension.equal_to("exe")) {} - else extension.equal_to("dll"); - to_return.original_name += extension; - - to_return.product_version = to_return.file_version; - to_return.product_version.set_component(version::REVISION, "0"); - to_return.product_version.set_component(version::BUILD, "0"); - - to_return.product_name = _ini->load(VERSION_SECTION, PRODUCT_KEY, ""); - to_return.company_name = _ini->load(VERSION_SECTION, COMPANY_KEY, ""); - to_return.copyright = _ini->load(VERSION_SECTION, COPYRIGHT_KEY, ""); - to_return.trademarks = _ini->load(VERSION_SECTION, LEGAL_INFO_KEY, ""); - to_return.web_address = _ini->load(VERSION_SECTION, WEB_SITE_KEY, ""); - - *_held_record = to_return; - _loaded = true; - return to_return; -} - -void version_ini::set_record(const version_record &to_write, bool write_ini) -{ - *_held_record = to_write; - if (write_ini) { - _ini->store(VERSION_SECTION, DESCRIPTION, to_write.description); - set_version(to_write.file_version, write_ini); - _ini->store(VERSION_SECTION, ROOT, to_write.original_name); - _ini->store(VERSION_SECTION, NAME, to_write.internal_name); - } - _loaded = true; // we consider this to be the real version now. -} - -////////////// - -const astring version_rc_template = "\ -#ifndef NO_VERSION\n\ -#include \n\ -#include <__build_version.h>\n\ -#include <__build_configuration.h>\n\ -#define BI_PLAT_WIN32\n\ - // force 32 bit compile.\n\ -1 VERSIONINFO LOADONCALL MOVEABLE\n\ -FILEVERSION __build_FILE_VERSION_COMMAS\n\ -PRODUCTVERSION __build_PRODUCT_VERSION_COMMAS\n\ -FILEFLAGSMASK 0\n\ -FILEFLAGS VS_FFI_FILEFLAGSMASK\n\ -#if defined(BI_PLAT_WIN32)\n\ - FILEOS VOS__WINDOWS32\n\ -#else\n\ - FILEOS VOS__WINDOWS16\n\ -#endif\n\ -FILETYPE VFT_APP\n\ -BEGIN\n\ - BLOCK \"StringFileInfo\"\n\ - BEGIN\n\ - // Language type = U.S. English(0x0409) and Character Set = Windows, Multilingual(0x04b0)\n\ - BLOCK \"040904b0\" // Matches VarFileInfo Translation hex value.\n\ - BEGIN\n\ - VALUE \"CompanyName\", __build_company \"\\000\"\n\ -#ifndef _DEBUG\n\ - VALUE \"FileDescription\", \"$file_desc\\000\"\n\ -#else\n\ - VALUE \"FileDescription\", \"$file_desc (DEBUG)\\000\"\n\ -#endif\n\ - VALUE \"FileVersion\", __build_FILE_VERSION \"\\000\" \n\ - VALUE \"ProductVersion\", __build_PRODUCT_VERSION \"\\000\" \n\ - VALUE \"InternalName\", \"$internal\\000\"\n\ - VALUE \"LegalCopyright\", __build_copyright \"\\000\"\n\ - VALUE \"LegalTrademarks\", __build_legal_info \"\\000\"\n\ - VALUE \"OriginalFilename\", \"$original_name\\000\"\n\ - VALUE \"ProductName\", __build_product_name \"\\000\"\n\ - $special_ole_flag\n\ - END\n\ - END\n\ -\n\ - BLOCK \"VarFileInfo\"\n\ - BEGIN\n\ - VALUE \"Translation\", 0x0409, 0x04b0 // US English (0x0409) and win32 multilingual (0x04b0)\n\ - END\n\ -END\n\ -#endif\n"; - -////////////// - -// replaces every occurrence of the keyword in "tag" with the "replacement". -#define REPLACE(tag, replacement) \ - new_version_entry.replace_all(tag, replacement); \ - -bool version_ini::write_rc(const version_record &to_write) -{ - astring new_version_entry(version_rc_template); - - // $file_ver -> w, x, y, z for version of the file. - REPLACE("$file_ver", to_write.file_version.flex_text_form(version::COMMAS)); - - // $prod_ver -> w, x, y, z for version of the product. - REPLACE("$prod_ver", to_write.product_version.flex_text_form - (version::COMMAS)); - - // $company -> name of company. - REPLACE("$company", to_write.company_name); - - // $file_desc -> description of file. - astring description_release = to_write.description; - REPLACE("$file_desc", description_release); - astring description_debug = to_write.description - + astring(" -- Debug Version"); - REPLACE("$file_desc", description_debug); - - // $file_txt_ver -> file version in form w.x.y.z. - REPLACE("$file_txt_ver", to_write.file_version.flex_text_form(version::DOTS)); - - // $internal -> internal name of the library, without extensions? - REPLACE("$internal", to_write.internal_name); - - // $copyright -> copyright held by us. - REPLACE("$copyright", to_write.copyright); - - // $legal_tm -> the legal trademarks that must be included, e.g. windows? - REPLACE("$legal_tm", to_write.trademarks); - - // $original_name -> the file's name before possible renamings. - REPLACE("$original_name", to_write.original_name); - - // $prod_name -> the name of the product that this belongs to. - REPLACE("$prod_name", to_write.product_name); - - // $prod_txt_ver -> product version in form w.x.y.z. - REPLACE("$prod_txt_ver", to_write.product_version - .flex_text_form(version::DOTS, version::MINOR)); - - astring special_filler; // nothing by default. - if (ole_auto_registering()) - special_filler = "VALUE \"OLESelfRegister\", \"\\0\""; - REPLACE("$special_ole_flag", special_filler); - - astring root_part = "/"; - root_part += _ini->load(VERSION_SECTION, ROOT, ""); - - astring rc_filename(astring(_path_name->dirname()) + root_part - + "_version.rc"); - - filename(rc_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); - // make sure we can write to the file. - - byte_filer rc_file(rc_filename, "w"); - if (!rc_file.good()) return false; - rc_file.write((abyte *)new_version_entry.s(), new_version_entry.length()); - rc_file.close(); - return true; -} - -////////////// - -const astring version_header_template = "\ -#ifndef $lib_prefix_VERSION_HEADER\n\ -#define $lib_prefix_VERSION_HEADER\n\ -\n\ -/*****************************************************************************\\\n\ -* *\n\ -* Name : Version header for $lib_name\n\ -* Author : Automatically generated by version_stamper *\n\ -* *\n\ -\\*****************************************************************************/\n\ -\n\ -#include <__build_version.h>\n\ -#include <__build_configuration.h>\n\ -#include \n\ -#include \n\ -\n\ -#ifdef __WIN32__\n\ -\n\ -// this macro can be used to check that the current version of the\n\ -// $lib_name library is the same version as expected. to use it, check\n\ -// whether it returns true or false. if false, the version is incorrect.\n\ -#define CHECK_$lib_prefix() (version_checker(astring(\"$lib_prefix\")\\\n\ - + astring(\".dll\"), version(__build_SYSTEM_VERSION),\\\n\ - astring(\"Please contact $company_name for the latest DLL and \"\\\n\ - \"Executable files ($web_address).\")).good_version())\n\ -\n\ -#else\n\ -\n\ -// null checking for embedded or other platforms without versions.\n\ -\n\ -#define CHECK_$lib_prefix() 1\n\ -\n\ -#endif //__WIN32__\n\ -\n\ -#endif\n\ -\n"; - -////////////// - -bool version_ini::write_code(const version_record &to_write) -{ - astring root_part = _ini->load(VERSION_SECTION, ROOT, ""); - astring root = root_part.upper(); // make upper case for naming sake. - astring name = _ini->load(VERSION_SECTION, NAME, ""); - // replace the macros here also. - name.replace_all("$product_name", to_write.product_name); - - astring new_version_entry(version_header_template); - -//some of the replacements are no longer needed. - - // $lib_prefix -> the first part of the library's name. - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - REPLACE("$lib_prefix", root); - - // $lib_name -> the name of the library, as it thinks of itself. - REPLACE("$lib_name", name); - REPLACE("$lib_name", name); - REPLACE("$lib_name", name); - - // $lib_version -> the current version for this library. - REPLACE("$lib_version", to_write.file_version.flex_text_form(version::COMMAS)); - - // $company_name -> the company that produces the library. - REPLACE("$company_name", to_write.company_name); - - // $web_address -> the web site for the company. not actually stored. - REPLACE("$web_address", to_write.web_address); - - astring header_filename(_path_name->dirname().raw() + "/" + root_part - + astring("_version.h")); - - filename(header_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); - // make sure we can write to the file. - - byte_filer header(header_filename, "w"); - if (!header.good()) return false; - header.write((abyte *)new_version_entry.s(), new_version_entry.length()); - header.close(); - return true; -} - - -// returns true if manipulated the full_string to replace its version -bool replace_version_entry(astring &full_string, const astring &look_for, - const astring &new_ver) -{ - bool to_return = false; - int posn = 0; // where are we looking for this? - - while (posn < full_string.length()) { - int ver_posn = full_string.find(look_for, posn); - if (ver_posn < 0) break; // nothing to modify. - int quote_posn = full_string.find("\"", ver_posn); - if (quote_posn < 0) break; // malformed assembly we will not touch. - int second_quote_posn = full_string.find("\"", quote_posn + 1); - if (second_quote_posn < 0) break; // more malformage. - full_string.zap(quote_posn + 1, second_quote_posn - 1); - full_string.insert(quote_posn + 1, new_ver); - to_return = true; // found a match. - // skip to the next place. - posn = quote_posn + new_ver.length(); - } - - return to_return; -} - -bool version_ini::write_assembly(const version_record &to_write, - bool do_logging) -{ -// FUNCDEF("write_assembly"); - filename just_dir = _path_name->dirname(); -//LOG(astring("dir is set to: ") + just_dir); - directory dir(just_dir); - filename to_patch; -//LOG(astring("dir has: ") + dir.files().text_form()); - if (non_negative(dir.files().find("AssemblyInfo.cpp"))) - to_patch = just_dir.raw() + "/AssemblyInfo.cpp"; - else if (non_negative(dir.files().find("AssemblyInfo.cs"))) - to_patch = just_dir.raw() + "/AssemblyInfo.cs"; - if (!to_patch.raw()) { - // no assembly file yet. see if there's one in a properties subdirectory. - directory dir2(just_dir + "/Properties"); - if (non_negative(dir2.files().find("AssemblyInfo.cpp"))) - to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cpp"; - else if (non_negative(dir2.files().find("AssemblyInfo.cs"))) - to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cs"; - } - - if (to_patch.raw().t()) { - // we have a filename to work on. - filename(to_patch).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); - // make sure we can write to the file. - byte_filer modfile(to_patch, "r+b"); - astring contents; - modfile.read(contents, 1000000); // read any file size up to that. - while (contents[contents.end()] == '\032') { - // erase any stray eof characters that may happen to be present. - contents.zap(contents.end(), contents.end()); - } -//LOG(astring("file contents are: \n") + contents); - -//here's where to fixit. - - astring ver_string = to_write.file_version.flex_text_form(version::DOTS); - bool did_replace = replace_version_entry(contents, "AssemblyVersionAttribute", ver_string); - if (!did_replace) { - did_replace = replace_version_entry(contents, "AssemblyVersion", ver_string); - } - if (!did_replace) return true; // nothing to modify? - did_replace = replace_version_entry(contents, "AssemblyFileVersion", ver_string); - if (!did_replace) { - did_replace = replace_version_entry(contents, "AssemblyFileVersionAttribute", ver_string); - } - // if we got to here, we at least replaced something... - -/* - int ver_posn = contents.find("AssemblyVersionAttribute", 0); - // try again if that seek failed. - if (ver_posn < 0) - ver_posn = contents.find("AssemblyVersion", 0); - if (ver_posn < 0) return true; // nothing to modify. -//LOG(astring("found assembly version: ") + to_patch); - int quote_posn = contents.find("\"", ver_posn); - if (quote_posn < 0) return true; // malformed assembly we will not touch. -//LOG(astring("found quote: ") + to_patch); - int second_quote_posn = contents.find("\"", quote_posn + 1); - if (second_quote_posn < 0) return true; // more malformage. -//LOG(astring("found quote: ") + to_patch); - contents.zap(quote_posn + 1, second_quote_posn - 1); - contents.insert(quote_posn + 1, ver_string); -*/ - -//LOG(astring("writing new output file: ") + to_patch); - modfile.seek(0); - modfile.write(contents); - modfile.truncate(); // chop off anything left from previous versions. - if (do_logging) { - // let the people know about this... - filename dirbase = filename(modfile.filename()).dirname().basename(); - filename just_base = filename(modfile.filename()).basename(); - program_wide_logger::get().log(astring(" patching: ") + dirbase - + "/" + just_base, basis::ALWAYS_PRINT); - } - } - - return true; -} - -bool version_ini::one_stop_version_stamp(const astring &path, - const astring &source_version, bool do_logging) -{ - astring path_name = path; - if (path_name.equal_to(".")) - path_name = application_configuration::current_directory(); - - // load the version record in from the ini file and cache it. - version_ini source(path_name); - source.get_record(); - - if (source_version.t()) { - // get the version structure from the passed file. - version_ini main_version(source_version); - version version_to_use = main_version.get_version(); - - // stuff the version from the main source into this particular file. - source.set_version(version_to_use, false); - - // stuff the other volatile records from the main version. - version_record main = main_version.get_record(); - source.access_record().company_name = main.company_name; - source.access_record().web_address = main.web_address; - source.access_record().copyright = main.copyright; - source.access_record().trademarks = main.trademarks; - source.access_record().product_name = main.product_name; - - source.access_record().internal_name.replace("$product_name", - source.get_record().product_name); - } - - if (do_logging) { - // report the current state. - program_wide_logger::get().log(source.get_record().internal_name + " version " - + source.get_version().text_form() + ".", ALWAYS_PRINT); - } - - version_ini verini(path_name); - verini.set_record(source.get_record(), false); - -// LOG(a_sprintf("The file \"%s\" contains this version information:", -// path_name.s())); -// LOG(verini.get_record().text_form()); - - if (!verini.write_rc(verini.get_record())) { - critical_events::alert_message(a_sprintf("Could not write the RC file in \"%s\".", - filename(path_name).basename().raw().s())); - return false; - } - - if (verini.library() && !verini.write_code(verini.get_record())) { - critical_events::alert_message(astring("Could not write the C++ header file for " - "the directory \"") - + filename(path_name).basename() + astring("\".\n")); - return false; - } - - if (!verini.write_assembly(verini.get_record(), do_logging)) { - critical_events::alert_message(astring("Could not write the Assembly info file for " - "the directory \"") - + filename(path_name).basename() + astring("\".\n")); - return false; - } - - return true; -} - -} //namespace. diff --git a/core/library/versions/version_ini.h b/core/library/versions/version_ini.h deleted file mode 100644 index 6838adee..00000000 --- a/core/library/versions/version_ini.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef VERSION_INI_CLASS -#define VERSION_INI_CLASS - -/*****************************************************************************\ -* * -* Name : version_ini editing support * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -namespace versions { - -//! This provides support for writing windows version files. -/*! - The class loads version information out of an initialization file and writes - it into a standard windows VERSION_INFO RC file entry. It also creates the - headers we use with our version control support. -*/ - -class version_ini : public virtual basis::root_object -{ -public: - version_ini(const basis::astring &path_name); - //!< the "path_name" is where the version INI file is located. - /*!< this file will be read for the key fields that will be used to name - the version RC file and generate information in the resource. */ - - ~version_ini(); - - DEFINE_CLASS_NAME("version_ini"); - - bool writable(); - //!< returns true if the INI file specified in the constructor is writable. - - structures::version get_version(); - //!< observes or modifies the version number structure held here. - /*!< if the record had not been previously loaded, it is loaded. */ - - void set_version(const structures::version &to_write, bool write_to_ini); - //!< sets the version held here. - /*!< if "write_ini" is true, then the ini file is written to also. note - that if get_record() had not previously been done and "write_ini" is not - true, then the next get_record() or get_version() will wipe out the - version that is set in the interim. */ - - structures::version_record get_record(); - //!< observes the entire version record. - /*!< The information is assembled from any cached record, the ini file and - other sources. if the record has been loaded before, the last loaded - version is returned plus any changes that have been made to the held - record since then. otherwise, the record is read from the ini file. */ - - structures::version_record &access_record(); - //!< provides access to change the version_record held here. - /*!< this does not read from the INI file, unlike get_record(). */ - - void set_record(const structures::version_record &to_write, bool write_to_ini); - //!< modifies the entire version record. - /*!< if "write_to_ini" is true, then the new information is actually - written to our configuration file. otherwise the information just - replaces the source record here. */ - - bool executable(); - //!< returns true if this version file is for an executable. - bool library(); - //!< returns true if this version file is for a dynamic library. - - bool ole_auto_registering(); - //!< returns true if this version file specifies ole auto registration. - - bool write_rc(const structures::version_record &to_write); - //!< writes out the file 'X_version.rc' for the X library or application. - /*!< the contents will include the information specified in "to_write", - where X is the library name from that record. */ - - bool write_code(const structures::version_record &to_write); - //!< writes out the header ('X_version.h') with the version information. - /*!< this file is needed for other libraries or application to know this - project's version number. the users can make sure that the header info - agrees with the actual version seen on the file. */ - - bool write_assembly(const structures::version_record &to_write, bool do_logging); - //!< fixes any assemblies with the info in "to_write". - - static bool executable(const basis::astring &path_name); - //!< returns true if "path_name" is for an executable. - static bool library(const basis::astring &path_name); - //!< returns true if "path_name" is for a dynamic library. - - structures::version read_version_from_ini(); - //!< specialized version ignores cache and gets version directly from file. - - static bool one_stop_version_stamp(const basis::astring &path, - const basis::astring &source_version, bool do_logging); - //!< performs version stamping using the ini file in "path". - /*!< "source_version" supplies the name of the main version file where - we retrieve the current version numbers. if that is not specified, then - only the version header and RC file are created. if "do_logging" is - true, then version info will be sent to the program-wide logger. */ - - // constants for strings found in the version INI file. - static const char *VERSION_SECTION; - static const char *COMPANY_KEY; - static const char *COPYRIGHT_KEY; - static const char *LEGAL_INFO_KEY; - static const char *PRODUCT_KEY; - static const char *WEB_SITE_KEY; - static const char *MAJOR; - static const char *MINOR; - static const char *REVISION; - static const char *BUILD; - static const char *DESCRIPTION; - static const char *ROOT; - static const char *NAME; - static const char *EXTENSION; - static const char *OLE_AUTO; - -private: - bool _loaded; //!< did we grab the data from the ini file yet? - filesystem::filename *_path_name; //!< where to find the ini file. - configuration::ini_configurator *_ini; //!< accesses the ini file. - structures::version_record *_held_record; //!< the data we cache for the ini file. - - static void check_name(filesystem::filename &to_examine); - //!< adds the default file name if "to_examine" turns out to be a directory. -}; - -} //namespace. - -#endif - diff --git a/core/makefile b/core/makefile deleted file mode 100644 index 2334be05..00000000 --- a/core/makefile +++ /dev/null @@ -1,7 +0,0 @@ -include variables.def - -PROJECT = core_modules -BUILD_BEFORE = library applications tools - -include rules.def - diff --git a/core/tools/clam_tools/makefile b/core/tools/clam_tools/makefile deleted file mode 100644 index b6869892..00000000 --- a/core/tools/clam_tools/makefile +++ /dev/null @@ -1,19 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -PROJECT = clam_tools -TYPE = application -DEFINITIONS += __BUILD_STATIC_APPLICATION__ - -# why was that there? -#LIBS_USED += pthread -# - -ifeq "$(OMIT_VERSIONS)" "" - SOURCE += clamtools_version.rc -endif -TARGETS = value_tagger.exe version_stamper.exe vsts_version_fixer.exe write_build_config.exe - -include cpp/rules.def - diff --git a/core/tools/clam_tools/value_tagger.cpp b/core/tools/clam_tools/value_tagger.cpp deleted file mode 100644 index df1724a4..00000000 --- a/core/tools/clam_tools/value_tagger.cpp +++ /dev/null @@ -1,1003 +0,0 @@ -/*****************************************************************************\ -* * -* Name : value_tagger * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Scoots through the entire known code base and builds a list of all the * -* outcome (and filter) values for that tree. A manifest of the names is * -* produced. Most of the behavior is driven by the ini file whose name is * -* passed on the command line. * -* Note that the set of items that can be searched for can be specified * -* in the ini file, although they must follow the format of: * -* pattern(name, value, description) * -* where the "pattern" is the search term and the other three items specify * -* the enumerated value to be marked. * -* * -******************************************************************************* -* Copyright (c) 2005-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef __WIN32__ - #include -#endif - -#undef LOG -#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), astring(s)) - -using namespace algorithms; -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace timely; - -const int LONGEST_SEPARATION = 128; - // the longest we expect a single line of text to be in definition blocks. - // if the definition of an outcome or whatever is farther away than this - // many characters from a comment start, we will no longer consider the - // line to be commented out. this pretty much will never happen unless it's - // intentionally done to break this case. - -const char *SKIP_VALUE_PHRASE = "SKIP_TO_VALUE"; - // the special phrase we use to indicate that values should jump to - // a specific number. - -//////////////////////////////////////////////////////////////////////////// - -// this object records all the data that we gather for the defined items. -class item_record -{ -public: - astring _name; - int _value; - astring _description; - astring _path; - astring _extra_tag; //!< records special info for links. - - item_record(const astring &name = astring::empty_string(), int value = 999, - const astring &description = astring::empty_string(), - const astring &path = astring::empty_string(), - const astring &extra_tag = astring::empty_string()) - : _name(name), _value(value), _description(description), _path(path), - _extra_tag(extra_tag) {} -}; - -//////////////////////////////////////////////////////////////////////////// - -class search_record -{ -public: - search_record(const astring &search = astring::empty_string(), - bool is_link = false, search_record *link = NIL) - : _search(search), _no_modify(false), _is_link(is_link), _our_link(link), - _current_value(0), _value_increment(1) {} - - // these properties are available for both real or linked records. - astring _search; // our term to search for in the files. - bool _no_modify; // true if values should not be automatically incremented. - astring _tag; // extra information attached to this type. - - bool is_link() const { return _is_link; } - // returns true if this object is leeching off another object for data. - - search_record *our_link() const { return _our_link; } - // returns the object that this object is a mere shadow of. - - symbol_table &definitions() { - if (is_link()) return _our_link->_definitions; - else return _definitions; - } - - int ¤t_value() { - if (is_link()) return _our_link->_current_value; - else return _current_value; - } - - int &value_increment() { - if (is_link()) return _our_link->_value_increment; - else return _value_increment; - } - - int_set &out_of_band() { - if (is_link()) return _our_link->_out_of_band; - else return _out_of_band; - } - -private: - bool _is_link; // true if this object links to another. - search_record *_our_link; // the search we share for our values. - symbol_table _definitions; - // the definitions that we found in the code. - int _current_value; // the next value to use for our term. - int _value_increment; - // how much to add for each new value, if this is an incrementing search. - int_set _out_of_band; - // values we've seen that were premature. we always want to honor this - // set, if it exists, but there will be nothing in it if the search has - // completely standard non-incrementing type. this could be varied by - // a non-incrementer linking to a standard incrementer. -}; - -//! a table of terms that we will search for in the code. -class active_searches : public symbol_table -{}; - -//////////////////////////////////////////////////////////////////////////// - -// this class provides us a way to easily sort our items based on value. - -class simple_sorter { -public: - int _index; - int _value; - simple_sorter(int index = 0, int value = 0) : _index(index), _value(value) {} - bool operator < (const simple_sorter &to_compare) const - { return _value < to_compare._value; } - bool operator == (const simple_sorter &to_compare) const - { return _value == to_compare._value; } -}; - -class sorting_array : public array {}; - -//////////////////////////////////////////////////////////////////////////// - -class value_tagger : public application_shell -{ -public: - value_tagger(); - virtual ~value_tagger(); - DEFINE_CLASS_NAME("value_tagger"); - int execute(); - int print_instructions_and_exit(); - - bool process_tree(const astring &path); - // called on each directory hierarchy that we need to process. - - bool process_file(const astring &path); - // examines the file specified to see if it matches our needs. - - bool parse_define(const astring &scanning, int indy, astring &name, - int &value, astring &description, int &num_start, int &num_end); - // processes the string in "scanning" to find parentheses surrounding - // the "name", "value" and "description". the "description" field may - // occupy multiple lines, so all are gathered together to form one - // unbroken string. the "num_start" and "num_end" specify where the - // numeric value was found, in case it needs to be patched. - -private: - ini_configurator *_ini; // the configuration for what we'll scan. - string_table _dirs; // the list of directories. - string_table _dirs_seen; // full list of already processed directories. - astring _manifest_filename; // the name of the manifest we'll create. - byte_filer _manifest; // the actual file we're building. - active_searches _search_list; // tracks our progress in scanning files. - int_array _search_ordering; - // lists the terms in the order they should be applied. initially this - // carries the first pass items, but later will be reset for second pass. - int_array _postponed_searches; - // lists the searches that must wait until the main search is done. - string_table _modified_files; // the list of files that we touched. -}; - -//////////////////////////////////////////////////////////////////////////// - -value_tagger::value_tagger() -: application_shell(), - _ini(NIL), - _dirs_seen(10) -{ -} - -value_tagger::~value_tagger() -{ - WHACK(_ini); -} - -int value_tagger::print_instructions_and_exit() -{ - LOG(a_sprintf("%s usage:", filename(_global_argv[0]).basename().raw().s())); - LOG(""); - LOG("\ -This utility scans a code base for outcome and filter definitions. It will\n\ -only scan the header files (*.h) found in the directories specified. The\n\ -single parameter is expected to be an INI filename that contains the scanning\n\ -configuration. The INI file should be formatted like this (where the $HOME\n\ -can be any variable substitution from the environment):"); - LOG(""); - LOG("\ -[manifest]\n\ -output=$HOME/manifest.txt\n\ -\n\ -[searches]\n\ -DEFINE_OUTCOME=1\n\ -DEFINE_FILTER=1\n\ -\n\ -[directories]\n\ -$HOME/source/lib_src/library/basis\n\ -$HOME/source/lib_src/library\n\ -$HOME/source/lib_src/communication/sockets\n\ -$HOME/source/lib_src/communication\n\ -$HOME/source/lib_src\n\ -$HOME/source/app_src\n\ -$HOME/source/test_src\n\ -\n\ -[DEFINE_OUTCOME]\n\ -first=0\n\ -increment=-1\n\ -\n\ -[DEFINE_FILTER]\n\ -first=-1\n\ -increment=1\n\ -no_modify=1\n\ -\n\ -[DEFINE_API_OUTCOME]\n\ -no_modify=1\n\ -link=DEFINE_OUTCOME\n\ -tag=API\n\ -\n\ - The \"first\" field defines the starting value that should be assigned to\n\ -items.\n\ - The \"increment\" field specifies what to add to a value for the next item.\n\ - The optional \"no_modify\" flag means that the values should not be auto-\n\ -incremented; their current value will be used.\n\ - The optional \"link\" field defines this type of item as using the current\n\ -values for another type of item. In this case, API_OUTCOME will use the\n\ -values for OUTCOME to share its integer space, but API_OUTCOME is not auto-\n\ -incremented even though OUTCOME is. This causes the values for OUTCOME and\n\ -API_OUTCOME to be checked for uniqueness together, but only OUTCOME will be\n\ -auto-incremented. Note that only one level of linking is supported currently.\n\ - The optional \"tag\" can be used to distinguish the entries for a particular\n\ -search type if needed. This is most helpful for links, so that they can be\n\ -distinguished from their base type.\n\ -\n\ -"); - - return 23; -} - -astring header_string(const astring &build_number) -{ - return a_sprintf("\ -#ifndef GENERATED_VALUES_MANIFEST\n\ -#define GENERATED_VALUES_MANIFEST\n\ -\n\ -// This file contains all outcomes and filters for this build.\n\ -\n\ -// Generated for build %s on %s\n\ -\n\ -", build_number.s(), time_stamp::notarize(true).s()); -} - -astring footer_string(const byte_array &full_config_file) -{ - return a_sprintf("\n\ -// End of definitions.\n\ -\n\ -\n\ -// The following is the full configuration for this build:\n\ -\n\ -/*\n\ -\n\ -%s\n\ -*/\n\ -\n\ -\n\ -#endif // outer guard.\n\ -", (char *)full_config_file.observe()); -} - -int value_tagger::execute() -{ - FUNCDEF("execute"); - if (_global_argc < 2) { - return print_instructions_and_exit(); - } - - log(time_stamp::notarize(true) + "value_tagger started.", basis::ALWAYS_PRINT); - - astring test_repository = environment::get("REPOSITORY_DIR"); - if (!test_repository) { - astring msg = "\ -There is a problem with a required build precondition. The following\r\n\ -variables must be set before the build is run:\r\n\ -\r\n\ - REPOSITORY_DIR This should point at the root of the build tree.\r\n\ -\r\n\ -There are also a few variables only required for CLAM-based compilation:\r\n\ -\r\n\ - MAKEFLAGS This should be set to \"-I $REPOSITORY_DIR/clam\".\r\n\ -\r\n\ -Note that on Win32 platforms, these should be set in the System or User\r\n\ -variables before running a build.\r\n"; -#ifdef __WIN32__ - ::MessageBox(0, to_unicode_temp(msg), - to_unicode_temp("Missing Precondition"), MB_ICONWARNING|MB_OK); -#endif - non_continuable_error(class_name(), func, msg); - } - - astring ini_file = _global_argv[1]; // the name of our ini file. - _ini = new ini_configurator(ini_file, ini_configurator::RETURN_ONLY); - - // read the name of the manifest file to create. - _manifest_filename = _ini->load("manifest", "output", ""); - if (!_manifest_filename) { - non_continuable_error(class_name(), ini_file, "The 'output' file entry is missing"); - } - _manifest_filename = parser_bits::substitute_env_vars(_manifest_filename); - - LOG(astring("Sending Manifest to ") + _manifest_filename); - LOG(""); - - filename(_manifest_filename).unlink(); - // clean out the manifest ahead of time. - - // read the list of directories to scan for code. - string_table temp_dirs; - bool read_dirs = _ini->get_section("directories", temp_dirs); - if (!read_dirs || !temp_dirs.symbols()) { - non_continuable_error(class_name(), ini_file, - "The 'directories' section is missing"); - } - for (int i = 0; i < temp_dirs.symbols(); i++) { -//log(astring("curr is ") + current); - astring current = parser_bits::substitute_env_vars(temp_dirs.name(i)); - _dirs.add(current, ""); - } - - LOG(astring("Directories to scan...")); - LOG(_dirs.text_form()); - - astring rdir = environment::get("REPOSITORY_DIR"); - astring fname; - astring parmfile = environment::get("BUILD_PARAMETER_FILE"); - if (parmfile.t()) fname = parmfile; - else fname = rdir + "/build.ini"; - - // read the list of search patterns. - string_table searches; - bool read_searches = _ini->get_section("searches", searches); - if (!read_searches || !searches.symbols()) { - non_continuable_error(class_name(), ini_file, - "The 'searches' section is missing"); - } - - LOG("Searching for..."); - LOG(searches.text_form()); - - // now make sure that we get the configuration for each type of value. - for (int i = 0; i < searches.symbols(); i++) { - const astring &curr_name = searches.name(i); - - search_record *check_search = _search_list.find(curr_name); - if (check_search) { - non_continuable_error(class_name(), ini_file, - astring("section ") + curr_name + " is being defined twice"); - } - - { - // check for whether this section is linked to another or not. - astring linked = _ini->load(curr_name, "link", ""); - search_record *our_link_found = NIL; - if (linked.t()) { - // we found that this should be linked to another item. - our_link_found = _search_list.find(linked); - if (!our_link_found) { - non_continuable_error(class_name(), ini_file, - astring("linked section ") + curr_name + " is linked to missing " - "section " + linked); - } - search_record new_guy(curr_name, true, our_link_found); - _search_list.add(curr_name, new_guy); - } else { - // this section is a stand-alone section. - search_record new_guy(curr_name); - _search_list.add(curr_name, new_guy); - } - } - - // find our new search cabinet again so we can use it. - search_record *curr_search = _search_list.find(curr_name); - if (!curr_search) { - non_continuable_error(class_name(), ini_file, - astring("section ") + curr_name + " is missing from table " - "after addition; logic error"); - } - - // specify some defaults first. - int start = 0; - int increm = 1; - if (!curr_search->is_link()) { - // a linked object doesn't get to specify starting value or increment. - start = _ini->load(curr_name, "first", start); - curr_search->current_value() = start; - increm = _ini->load(curr_name, "increment", increm); - curr_search->value_increment() = increm; - } else { - start = curr_search->our_link()->current_value(); - increm = curr_search->our_link()->value_increment(); - } - - int no_modify = _ini->load(curr_name, "no_modify", 0); - if (no_modify) { - curr_search->_no_modify = true; - } - - astring tag = _ini->load(curr_name, "tag", ""); - if (tag.t()) { - curr_search->_tag = tag; - } - - a_sprintf to_show("%s: no_modify=%s", curr_name.s(), - no_modify? "true" : "false"); - - if (curr_search->is_link()) { - // links show who they're hooked to. - to_show += astring(" link=") + curr_search->our_link()->_search; - } else { - // non-links get to show off their start value and increment. - to_show += a_sprintf(" start=%d increment=%d", start, increm); - } - if (tag.t()) { - to_show += astring(" tag=") + curr_search->_tag; - } - LOG(to_show); - } - LOG(""); - - // now gather some info about the build that we can plug into the manifest. - - byte_filer build_file(fname, "r"); - if (!build_file.good()) { - non_continuable_error(class_name(), build_file.filename(), - "Could not find the build configuration; is REPOSITORY_DIR set?"); - } - byte_array full_config; - build_file.read(full_config, 100000); // a good chance to be big enough. - build_file.close(); - -//log("got config info:"); -//log((char *)full_config.observe()); - - astring build_number; - ini_configurator temp_ini(fname, configurator::RETURN_ONLY); - build_number += temp_ini.load("version", "major", ""); - build_number += "."; - build_number += temp_ini.load("version", "minor", ""); - build_number += "."; - build_number += temp_ini.load("version", "revision", ""); - build_number += "."; - build_number += temp_ini.load("version", "build", ""); - if (build_number.equal_to("...")) { - non_continuable_error(class_name(), build_file.filename(), - "Could not read the build number; is build parameter file malformed?"); - } - -//log(astring("got build num: ") + build_number); - - // now that we know what file to create, write the header blob for it. - _manifest.open(_manifest_filename, "wb"); - if (!_manifest.good()) { - non_continuable_error(class_name(), _manifest_filename, - "Could not write to the manifest file!"); - } - _manifest.write(header_string(build_number)); - - // make sure we have the right ordering for our terms. items that are - // non-modify types must come before the modifying types. - for (int i = 0; i < _search_list.symbols(); i++) { - search_record &curr_reco = _search_list[i]; - if (curr_reco._no_modify) - _search_ordering += i; - else - _postponed_searches += i; - } - - // scan across each directory specified for our first pass. - LOG("First pass..."); - for (int i = 0; i < _dirs.symbols(); i++) { - if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue; // skip comment. - LOG(astring(" Processing: ") + _dirs.name(i)); - bool ret = process_tree(_dirs.name(i)); - if (!ret) { - LOG(astring("Problem encountered in directory ") + _dirs.name(i)); - } - } - LOG(""); - - // second pass now. - LOG("Second pass..."); - _search_ordering = _postponed_searches; // recharge the list for 2nd pass. - _dirs_seen.reset(); // drop any directories we saw before. - for (int i = 0; i < _dirs.symbols(); i++) { - if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue; // skip comment. - LOG(astring(" Processing: ") + _dirs.name(i)); - bool ret = process_tree(_dirs.name(i)); - if (!ret) { - LOG(astring("Problem encountered in directory ") + _dirs.name(i)); - } - } - LOG(""); - - const astring quote = "\""; - const astring comma = ","; - - // scoot across all the completed searches and dump results. - for (int i = 0; i < _search_list.symbols(); i++) { - search_record &curr_reco = _search_list[i]; - const astring &pattern = curr_reco._search; - - _manifest.write(astring("/* START ") + pattern + "\n"); - _manifest.write(astring("[") + pattern + "]\n"); - - if (!curr_reco.is_link()) { - // scoot across all definitions and print them out. - - // do the print out in order, as dictated by the sign of the increment. - sorting_array sortie; - for (int j = 0; j < curr_reco.definitions().symbols(); j++) { - const item_record &rec = curr_reco.definitions().get(j); - sortie += simple_sorter(j, rec._value); - } - shell_sort(sortie.access(), sortie.length(), - negative(curr_reco.value_increment())); - - for (int j = 0; j < sortie.length(); j++) { - int indy = sortie[j]._index; - const item_record &rec = curr_reco.definitions().get(indy); - astring to_write = " "; - if (rec._extra_tag.t()) { - to_write += astring("(") + rec._extra_tag + ") "; - } - to_write += quote + rec._name + quote + comma + " "; - to_write += quote + a_sprintf("%d", rec._value) + quote + comma + " "; - to_write += quote + rec._description + quote + comma + " "; - to_write += quote + rec._path + quote; - to_write += "\n"; - _manifest.write(to_write); - } - } else { - // this is just a link. - astring to_write = " Linked to search item "; - to_write += curr_reco.our_link()->_search; - to_write += "\n"; - _manifest.write(to_write); - } - - _manifest.write(astring("END ") + pattern + " */\n\n"); - } - - _manifest.write(footer_string(full_config)); - - // show all the modified files. - if (_modified_files.symbols()) { - const int syms = _modified_files.symbols(); - LOG("Modified Files:"); - LOG("==============="); - for (int i = 0; i < syms; i++) { - LOG(_modified_files.name(i)); - } - } else { - LOG("No files needed modification for generated values."); - } - LOG(""); - - log(time_stamp::notarize(true) + "value_tagger finished.", ALWAYS_PRINT); - - return 0; -} - -#define INBO (indy < scanning.length()) - // a macro that makes length checking less verbose. - -// make sure we drop any spaces in between important bits. -#define SKIP_SPACES \ - while (INBO && parser_bits::white_space(scanning[indy])) indy++; - -// return with a failure but say why it happened. -#define FAIL_PARSE(why) { \ - log(astring("failed to parse the string because ") + why + ".", ALWAYS_PRINT); \ - return false; \ -} - -bool value_tagger::parse_define(const astring &scanning, int indy, - astring &name, int &value, astring &description, int &num_start, - int &num_end) -{ - // prepare our result objects. - name = ""; value = -1; description = ""; num_start = -1; num_end = -1; - - SKIP_SPACES; - - // look for starting parenthesis. - if (!INBO || (scanning[indy] != '(') ) - FAIL_PARSE("the first parenthesis is missing"); - - indy++; // skip paren. - SKIP_SPACES; - - // find the name of the item being defined. - while (INBO && (scanning[indy] != ',') ) { - name += scanning[indy]; - indy++; - } - - indy++; // skip the comma. - SKIP_SPACES; - - astring num_string; - num_start = indy; - while (INBO && parser_bits::is_numeric(scanning[indy])) { - num_string += scanning[indy]; - indy++; - } - num_end = indy - 1; - value = num_string.convert(0); - - SKIP_SPACES; - - if (!INBO || (scanning[indy] != ',') ) - FAIL_PARSE("the post-value comma is missing"); - - indy++; - SKIP_SPACES; - - if (!INBO || (scanning[indy] != '"') ) - FAIL_PARSE("the opening quote for the description is missing"); - - indy++; // now we should be at raw text. - - // scan through the full description, taking into account that it might - // be broken across multiple lines as several quoted bits. - bool in_quote = true; // we're inside a quote now. - while (INBO && (scanning[indy] != ')') ) { - const char curr = scanning[indy]; -//hmmm: escaped quotes are not currently handled. - if (curr == '"') in_quote = !in_quote; // switch quoting state. - else if (in_quote) description += curr; - indy++; - } - - return scanning[indy] == ')'; -} - -bool value_tagger::process_file(const astring &path) -{ - byte_filer examining(path, "rb"); - if (!examining.good()) { - log(astring("Error reading file: ") + path, ALWAYS_PRINT); - return false; - } - examining.seek(0, byte_filer::FROM_END); - int fsize = int(examining.tell()); - examining.seek(0, byte_filer::FROM_START); - - astring contents('\0', fsize + 20); - int bytes_read = examining.read((abyte *)contents.access(), fsize); - // read the file directly into a big astring. - examining.close(); - contents[bytes_read] = '\0'; - contents.shrink(); // drop any extra stuff at end. - - bool modified = false; // set to true if we need to write the file back. - - // check if the file matches our phrases of interest. - bool matched = false; - for (int q = 0; q < _search_list.symbols(); q++) { - search_record &curr_reco = _search_list[q]; - if (contents.contains(curr_reco._search)) { -//_manifest.write(astring("MATCH-") + curr_pattern + ": " + path + "\n" ); //temp - matched = true; - break; - } - } - - if (!matched) return true; - - // now we have verified that there's something interesting in this file. - // go through to find the interesting bits. - - // we do this in the search ordering that we established earlier, so we - // will tag the values in the proper order. - for (int x = 0; x < _search_ordering.length(); x++) { - int q = _search_ordering[x]; // get our real index. - search_record &curr_reco = _search_list[q]; - const astring &curr_pattern = curr_reco._search; -///log(astring("now seeking ") + curr_pattern); - int start_from = 0; // where searches will start from. - - while (true) { - // search forward for next match. - int indy = contents.find(curr_pattern, start_from); - if (negative(indy)) break; // no more matches. - start_from = indy + 5; // ensure we'll skip past the last match. - - // make sure our deadly pattern isn't in front; we don't want to - // process the actual definition of the macro in question. -//log(a_sprintf("indy=%d [indy-1]=%c [indy-2]=%c", indy, contents[indy-1], contents[indy-2])); - if ( (indy > 3) && (contents[indy-1] == ' ') - && (contents[indy-2] == 'e') ) { - int def_indy = contents.find("#define", indy, true); -//log(astring("checking ") + curr_pattern + a_sprintf(": defindy %d, ", def_indy) + path + "\n" ); - - if (non_negative(def_indy) && (absolute_value(indy - def_indy) < 12) ) { - // they're close enough that we probably need to skip this - // occurrence of our search term. -//_manifest.write(astring("DEMATCH-") + curr_pattern + ": had the #define! " + path + "\n" ); - continue; - } - } - - // make sure we don't include commented lines in consideration. - int comm_indy = contents.find("//", indy, true); - if (non_negative(comm_indy)) { -//log("found a comment marker"); - // we found a comment before the definition, but we're not sure how - // far before. - if (absolute_value(comm_indy - indy) < LONGEST_SEPARATION) { -//log("comment is close enough..."); - // they could be on the same line... unless lines are longer than - // our constant. - bool found_cr = false; - for (int q = comm_indy; q < indy; q++) { - if (parser_bits::is_eol(contents[q])) { - found_cr = true; - break; - } - } - if (!found_cr) { - // if there's a comment before the definition and no carriage - // returns in between, then this is just a comment. -//log(astring("DEMATCH-") + curr_pattern + ": had the comment! " + path + "\n" ); - continue; - } - } - } - - // now we are pretty sure this is a righteous definition of an outcome, - // and not the definition of the macro itself. - int value, num_start, num_end; - astring name, description; - bool found_it = parse_define(contents, indy + curr_pattern.length(), - name, value, description, num_start, num_end); - if (!found_it) { - log(astring("there was a problem parsing ") + curr_pattern + " in " + path, ALWAYS_PRINT); - continue; - } - - // handle the special keyword for changing the value. this is useful - // if you want a set of outcomes to start at a specific range. - if (name.equal_to(SKIP_VALUE_PHRASE)) { - LOG(astring("\tSkipping value for ") + curr_pattern - + a_sprintf(" to %d because of request in\n\t", value) + path); - curr_reco.current_value() = value; - } - while (true) { - // make sure that the current value is not already in use. - if (!curr_reco.out_of_band().member(curr_reco.current_value())) - break; - // if we had a match above, we need to adjust the current value. - curr_reco.current_value() += curr_reco.value_increment(); - } - if (name.equal_to(SKIP_VALUE_PHRASE)) { - continue; // keep going now that we vetted the current value. - } - -//must catch some conditions here for values: -// for incrementing types, we can always just try to use the next value -// once we know it wasn't already defined out of band? -// for non-incrementing types, we need to ensure we haven't already seen -// the thing. do we just always add a value seen to out of band? -// for mixed types, the incrementing side needs to not reuse out of band -// values. - - astring other_place; // the other place it was defined. - if (curr_reco.out_of_band().member(value) && curr_reco._no_modify) { - // this is bad; we have already seen this value elsewhere... - for (int x = 0; x < curr_reco.definitions().symbols(); x++) { - // see if we can find the previous definition in our list. - if (value == curr_reco.definitions()[x]._value) - other_place = curr_reco.definitions()[x]._path; - } - non_continuable_error(class_name(), path, - a_sprintf("There is a duplicate value here for %s=%d ! " - "Also defined in %s.", name.s(), value, other_place.s())); - } - - // we care sometimes that this value is different than the next - // sequential one we'd assign. if it's a non-modifying type of - // search, then we can't change the assigned value anyway--we can - // only report the error in re-using a value (above). - if (!curr_reco._no_modify) { - // check that the defined value matches the next one we'd assign. - if (value != curr_reco.current_value()) { - // patch the value with the appropriate one we've been tracking. - modified = true; - value = curr_reco.current_value(); - contents.zap(num_start, num_end); // remove old fusty value. - contents.insert(num_start, a_sprintf("%d", value)); - _modified_files.add(path, ""); - } - // move the current value up (or down). - curr_reco.current_value() += curr_reco.value_increment(); - } else { - // non-modifying type of value here. -//anything to do? - } - - curr_reco.out_of_band() += value; - // we've vetted the value, and now we're definitely using it. - - // make sure they aren't trying to reuse the name for this item. - item_record rec; - bool found_name = false; // we don't want to find name already there. - if (curr_reco.definitions().find(name)) { - rec = *curr_reco.definitions().find(name); - found_name = true; - } - if (found_name) { - // this is bad. this means we are not unique. remove the manifest - // file due to this error. - _manifest.close(); // close the file since we want to whack it. - filename(_manifest_filename).unlink(); - non_continuable_error(class_name(), path, - a_sprintf("There is a duplicate name here (%s)! " - "Also defined in %s.", name.s(), rec._path.s())); - } - - // record the definition in the appropriate table. - curr_reco.definitions().add(name, item_record(name, value, - description, path, curr_reco._tag)); - -//log(curr_pattern + a_sprintf(": name=%s value=%d desc=[%s]\n", name.s(), value, description.s())); - - } - } - - if (modified) { - // rewrite the file, since we modified its contents. - bool chmod_result = filename(path).chmod(filename::ALLOW_BOTH, - filename::USER_RIGHTS); -/* - int chmod_value; -#ifdef __UNIX__ - chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; -#elif defined(__WIN32__) - chmod_value = _S_IREAD | _S_IWRITE; -#else - //unknown. let's try unix... - chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; -#endif - int chmod_result = chmod(path.s(), chmod_value); -*/ - if (!chmod_result) { - log(astring("there was a problem changing permissions on ") + path - + "; writing the new version might fail.", ALWAYS_PRINT); - } - - byte_filer rewriting(path, "wb"); - rewriting.write(contents); - rewriting.close(); - } - - return true; -} - -bool value_tagger::process_tree(const astring &path) -{ - directory_tree dir(path, "*.h"); - if (!dir.good()) return false; - - dir_tree_iterator *ted = dir.start(directory_tree::prefix); - // create our iterator to perform a prefix traversal. - - filename curr_dir; // the current path the iterator is at. - string_array files; // the filenames held at the iterator. - - while (directory_tree::current(*ted, curr_dir, files)) { - // we have a good directory to process. - - // omit any subdirectories that exactly match directories we've already - // scanned. necessary to avoid redoing whole areas. - if (!_dirs_seen.find(curr_dir)) { - // deal with each matching header file we've found. - for (int i = 0; i < files.length(); i++) { - bool file_ret = process_file(filename(curr_dir.raw(), files[i])); - if (!file_ret) { - log(astring("There was an error while processing ") + files[i], ALWAYS_PRINT); - } - } - - _dirs_seen.add(curr_dir, ""); - } - - // go to the next place. - directory_tree::next(*ted); - } - - directory_tree::throw_out(ted); - return true; -} - -HOOPLE_MAIN(value_tagger, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/clam_tools/version.ini b/core/tools/clam_tools/version.ini deleted file mode 100644 index 1ae20aa5..00000000 --- a/core/tools/clam_tools/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description = CLAM Tools for Building -root = clamtools -name = CLAM Tools -extension = exe - diff --git a/core/tools/clam_tools/version_stamper.cpp b/core/tools/clam_tools/version_stamper.cpp deleted file mode 100644 index a668a9f0..00000000 --- a/core/tools/clam_tools/version_stamper.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/*****************************************************************************\ -* * -* Name : version_stamper * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef LOG -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger(), to_print) - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace versions; - -//! This class creates resource information for applications and libraries to be version stamped. -/*! - This creates a resource (.rc) file and a C++ header (.h) file when given the directory where - a version information file (version.ini) resides. It creates the files in that directory. -*/ - -class version_stamper : public application_shell -{ -public: - version_stamper(); - ~version_stamper(); - - DEFINE_CLASS_NAME("version_stamper"); - - int execute(); - // performs the main action of creating resource and code files. -}; - -////////////// - -version_stamper::version_stamper() : application_shell() -{ -} - -version_stamper::~version_stamper() {} - -int version_stamper::execute() -{ -/// FUNCDEF("execute"); - SETUP_CONSOLE_LOGGER; // override the file_logger from app_shell. - if (application::_global_argc < 2) { - log(astring("The directory where the 'version.ini' file is located\n" - "must be specified as the first parameter of this program. Another\n" - "version file may optionally be specified as the second parameter of\n" - "the program; the version contained in this file will be used to set\n" - "the version of the file specified in the first parameter.\n" - "Additionally, if the environment variable 'DEBUG' exists, then the\n" - "generated RC file will be marked as a debug build. Otherwise it is\n" - "marked as a release build. Note that the CLAM system automatically\n" - "sets this for you.\n\n"), ALWAYS_PRINT); - return 1; - } - - astring path_name = application::_global_argv[1]; - astring source_version_file; // blank by default. - if (application::_global_argc > 2) - source_version_file = application::_global_argv[2]; - bool ret = version_ini::one_stop_version_stamp(path_name, source_version_file, true); - if (!ret) return 1; // failure. - return 0; // success. -} - -HOOPLE_MAIN(version_stamper, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/clam_tools/vsts_version_fixer.cpp b/core/tools/clam_tools/vsts_version_fixer.cpp deleted file mode 100644 index 8fc8d3cd..00000000 --- a/core/tools/clam_tools/vsts_version_fixer.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/*****************************************************************************\ -* * -* Name : vsts_version_fixer * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 2008-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef LOG -#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) -#undef BASE_LOG -#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) - -using namespace application; -//using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; -using namespace timely; -using namespace versions; - -//#define DEBUG_VSTS_VERSION_FIXER - // uncomment for noisy version. - -//////////////////////////////////////////////////////////////////////////// - -class vsts_version_fixer : public application::application_shell -{ -public: - vsts_version_fixer() : application_shell() {} - virtual ~vsts_version_fixer() {} - - virtual int execute(); - - DEFINE_CLASS_NAME("vsts_version_fixer"); - - void remove_confusing_files(); - //!< tosses out the generated files that confuse ms devstudio. - -//move these - typedef bool spider_method(const directory ¤t); - //!< prototype for functions that are called during directory spidering. - /*!< this function should do whatever work is needed on the items in - that "current" directory. true should be returned by this method when - the traversal of the directory is still desired. if there is a reason - to stop traversing the directory hierarchy, then it should return false. */ - -//hmmm: support postfix and in order also. -//hmmm: support reporting where the spidering stopped. - bool spider_directory(directory start, spider_method to_invoke); - //!< traverses hierarchy "start" in prefix order while calling "to_invoke". - /*!< true is returned if all invoked spider methods returned true. - otherwise, false is returned. */ -//move those - - bool perform_version_stamping(const filename &start_name); - //!< finds all version ini files and applies stamps using them. - - void whack_in_subdirs(const directory &start, - const string_array &file_whacks, const string_array &dir_whacks); - //!< recursively cleans all items found in "file_whacks" and "dir_whacks". - /*!< "file_whacks" is a list of file suffixes to whack. for example, to - remove all files matching a pattern *.exe, pass in just ".exe" in the - "file_whacks". the "dir_whacks" list is a list of directories to - completely obliterate where found. */ -}; - -HOOPLE_MAIN(vsts_version_fixer, ) - -//////////////////////////////////////////////////////////////////////////// - -//hmmm: move to a useful place; maybe even in directory class? -bool vsts_version_fixer::spider_directory(directory start, - spider_method to_invoke) -{ -// FUNCDEF("spider_directory"); - - using namespace basis; - -//LOG(astring("spider_directory: ") + start.path()); - // call our method on this directory first. this ensures that we have - // dealt with it before we spider off elsewhere. - bool ret = to_invoke(start); - if (!ret) return false; // bail. - - // now let's look at the subdirectories. we'll recurse on all of them in - // the order listed. - const string_array &dirs = start.directories(); -//LOG(astring("dirs found to spider: ") + dirs.text_form()); - for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) { - const astring ¤t_dir = dirs[dir_indy]; -//LOG(astring("currdir into ") + current_dir); - if (current_dir.equal_to(".svn")) continue; // skip this. - if (current_dir.equal_to("CVS")) continue; // skip this also. - directory new_dir(start.path() + "/" + current_dir, start.pattern().observe()); - bool ret = spider_directory(new_dir, to_invoke); - if (!ret) return false; // bail from subdir issue. - } - // if we made it to here, everything was groovy. - return true; -} - -//////////////////////////////////////////////////////////////////////////// - -#define static_class_name() "vsts_version_fixer" - -// global variables used to communicate with whacking_spider. -string_array global_file_whacks; -string_array global_dir_whacks; - -bool whacking_spider(const directory ¤t) -{ -// FUNCDEF("whacking_spider"); - using namespace basis; -//LOG(astring("whacking_spider: ") + current.path()); - // iterate across the files in the directory and check for evil ones. - const string_array &files = current.files(); - for (int file_indy = 0; file_indy < files.length(); file_indy++) { - const astring ¤t_file = files[file_indy]; -//LOG(astring("currfile ") + current_file); - // now iterate across our pattern list to see if this thing is - // one of the offending files. - for (int pat_indy = 0; pat_indy < global_file_whacks.length(); pat_indy++) { -//LOG(astring("currpat ") + global_file_whacks[pat_indy]); - if (current_file.iends(global_file_whacks[pat_indy])) { - filename goner(current.path() + "/" + current_file); - BASE_LOG(astring("whack file: ") + goner.raw()); - goner.unlink(); - break; // stop looking at the pattern list for matches. - } - } - } - - // okay, now that we've cleaned out those files, let's look at the - // subdirectories. - const string_array &dirs = current.directories(); - for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) { - const astring ¤t_dir = dirs[dir_indy]; -//LOG(astring("currdir ") + current_dir); - for (int pat_indy = 0; pat_indy < global_dir_whacks.length(); pat_indy++) { - if (current_dir.iequals(global_dir_whacks[pat_indy])) { - filename goner(current.path() + "/" + current_dir); - BASE_LOG(astring("whack dir: ") + goner.raw()); -//hmmm: plug in recursive delete here instead. -basis::un_int kid; -launch_process::run("rm", astring("-rf ") + goner.raw(), launch_process::AWAIT_APP_EXIT, kid); - break; // skip remainder of patterns for this dir. - } - } - } - return true; -} - -#undef static_class_name - -//////////////////////////////////////////////////////////////////////////// - -void vsts_version_fixer::whack_in_subdirs(const directory &start, - const string_array &file_whacks, const string_array &dir_whacks) -{ - FUNCDEF("whack_in_subdirs"); - using namespace basis; - - // save the lists so the spider method can see them. - // note that this approach with a global variable would be bad if there - // were concurrent invocations of the spidering, but we're not doing - // that here. - global_file_whacks = file_whacks; - global_dir_whacks = dir_whacks; - - bool worked = spider_directory(start, whacking_spider); - if (!worked) { - LOG(astring("spidering of ") + start.path() + " failed for some reason."); - } -} - -//////////////////////////////////////////////////////////////////////////// - -#define static_class_name() "vsts_version_fixer" - -basis::astring global_build_ini; - -bool stamping_spider(const directory ¤t) -{ -// FUNCDEF("stamping_spider"); - using namespace basis; -//LOG(astring("stamping_spider: ") + current.path()); - - const string_array &files = current.files(); - for (int file_indy = 0; file_indy < files.length(); file_indy++) { - const astring ¤t_file = files[file_indy]; -//LOG(astring("currfile ") + current_file); - // we won't process the "core_version.ini" file, which is a special - // case that is somewhat well known as not being a file used (by us) - // for dlls. - if (current_file.ends("version.ini") - && !current_file.iequals("core_version.ini") ) { -//LOG(astring("found ver file: ") + current.path() + "/" + current_file); - version_ini::one_stop_version_stamp(current.path() + "/" + current_file, - global_build_ini, true); - } - } - return true; -} - -#undef static_class_name - -//////////////////////////////////////////////////////////////////////////// - -bool vsts_version_fixer::perform_version_stamping(const filename &start_name) -{ -// FUNCDEF("perform_version_stamping"); - directory start(start_name); - return spider_directory(start, stamping_spider); -} - -//////////////////////////////////////////////////////////////////////////// - -void vsts_version_fixer::remove_confusing_files() -{ - using namespace basis; - // clean out a few directories that show up in the source tree from c# - // projects compilation. c# projects always rebuild every time anyways, - // so this doesn't lose us any compilation time. the only thing c# - // projects don't ever seem to rebuild is their version resource, unless - // they're forced to totally recompile like we cause below. - string_array source_file_whacks; // none right now. - string_array source_dir_whacks; - source_dir_whacks += "obj"; - source_dir_whacks += "Debug"; - source_dir_whacks += "Release"; - source_dir_whacks += "bin"; - source_dir_whacks += "temp_build"; - directory repo_source(environment::get("REPOSITORY_DIR") + "/source"); - whack_in_subdirs(repo_source, source_file_whacks, source_dir_whacks); - directory libra_src(environment::get("REPOSITORY_DIR") + "/libraries"); - whack_in_subdirs(libra_src, source_file_whacks, source_dir_whacks); - directory produ_src(environment::get("REPOSITORY_DIR") + "/products"); - whack_in_subdirs(produ_src, source_file_whacks, source_dir_whacks); - -/* this never helped. - // clean out a variety of bad files in the objects hierarchy. - // currently this is just the generated RES files which we have seen cause - // vsts to think apps and dlls are up to date when they are actually not. - directory repo_objects(environment::get("REPOSITORY_DIR")); - string_array objects_file_whacks; - objects_file_whacks += ".res"; - string_array objects_dir_whacks; // none right now. - whack_in_subdirs(repo_objects, objects_file_whacks, objects_dir_whacks); -*/ -} - -int vsts_version_fixer::execute() -{ - FUNCDEF("execute"); - using namespace basis; - log(time_stamp::notarize(true) + "vsts_version_fixer started.", ALWAYS_PRINT); - - remove_confusing_files(); - - astring repo_dir = environment::get("REPOSITORY_DIR"); - - // figure out which build parameter file to use. - global_build_ini = ""; - astring parmfile = environment::get("BUILD_PARAMETER_FILE"); - if (parmfile.t()) { - global_build_ini = parmfile; -LOG(astring("found parm variable ") + parmfile); - } else { - // they didn't specify the file. argh. - global_build_ini = repo_dir + "/production/feisty_meow_config.ini"; - if (!filename(global_build_ini).exists()) { -LOG(astring("guess not found: ") + global_build_ini); - LOG("cannot locate the build configuration file."); - return 3; - } - } - - // now stamp versions on everything we can find. - filename repo_source = repo_dir + "/../../libraries"; - if (!repo_source.exists()) { - repo_source = repo_dir + "/source"; - if (!repo_source.exists()) { - LOG("cannot locate the main library source location."); - return 3; - } - } -LOG(astring("chose source dir as ") + repo_source); - perform_version_stamping(repo_source); - - filename repo_apps = repo_dir + "/../../products"; - if (repo_apps.exists()) { - perform_version_stamping(repo_apps); - } - log(time_stamp::notarize(true) + "vsts_version_fixer finished.", ALWAYS_PRINT); - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/clam_tools/write_build_config.cpp b/core/tools/clam_tools/write_build_config.cpp deleted file mode 100644 index 53cfa0a4..00000000 --- a/core/tools/clam_tools/write_build_config.cpp +++ /dev/null @@ -1,434 +0,0 @@ -/*****************************************************************************\ -* * -* Name : write_build_config * -* Author : Chris Koeritz * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include "write_build_config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace configuration; -using namespace filesystem; -using namespace loggers; -using namespace structures; -using namespace textual; -using namespace versions; - -const int MAX_LINE_SIZE = 2048; - //!< we should never see an ini line longer than this. - -const int MAX_HEADER_FILE = 128 * KILOBYTE; - //!< an excessively long allowance for the maximum generated header size. - -const char *DEFINITIONS_STATEMENT = "DEFINITIONS"; - //!< the tag we see in the config file for directly compatible macros. - -const char *EXPORT_STATEMENT = "export "; - //!< a tag we see on variables to be inherited by subshells - -// make conditionals that we will eat. -const char *IFEQ_STATEMENT = "ifeq"; -const char *IFNEQ_STATEMENT = "ifneq"; -const char *ENDIF_STATEMENT = "endif"; - -#undef LOG -#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) - -write_build_config::write_build_config() -: application_shell(), - _end_matter(new astring), - _ver(new version), - _nesting(0) -{} - -write_build_config::~write_build_config() -{ - WHACK(_end_matter); - WHACK(_ver); -} - -const string_set &write_build_config::exclusions() -{ - static string_set _hidden; - static bool _initted = false; - if (!_initted) { - _hidden += "DEBUG"; - _hidden += "OPTIMIZE"; - _hidden += "STRICT_WARNINGS"; - } - return _hidden; -} - -// adds some more material to dump at the end of the file. -#define ADD_COMMENT_RETURN(sym, val) { \ - *_end_matter += astring(" ") + sym + " = " + val + "\n"; \ - return common::OKAY; \ -} - -outcome write_build_config::output_macro(const astring &symbol, - const astring &value, astring &accumulator) -{ - // drop any excluded items to avoid interfering with devstu solution. - if (exclusions().member(symbol)) - ADD_COMMENT_RETURN(symbol, value); - // drop any malformed symbols or values. - if (symbol.contains("\"") || value.contains("\"")) - ADD_COMMENT_RETURN(symbol, value); - accumulator += " #ifndef "; - accumulator += symbol; - accumulator += "\n"; - accumulator += " #define "; - accumulator += symbol; - accumulator += " \""; - accumulator += value; - accumulator += "\"\n"; - accumulator += " #endif\n"; - return common::OKAY; -} - -bool write_build_config::process_version_parts(const astring &symbol, - const astring &value) -{ - if (symbol.equal_to("major")) - { _ver->set_component(version::MAJOR, value); return true; } - if (symbol.equal_to("minor")) - { _ver->set_component(version::MINOR, value); return true; } - if (symbol.equal_to("revision")) - { _ver->set_component(version::REVISION, value); return true; } - if (symbol.equal_to("build")) - { _ver->set_component(version::BUILD, value); return true; } - return false; -} - -bool write_build_config::check_nesting(const astring &to_check) -{ - if (to_check.compare(IFEQ_STATEMENT, 0, 0, int(strlen(IFEQ_STATEMENT)), true) - || to_check.compare(IFNEQ_STATEMENT, 0, 0, int(strlen(IFNEQ_STATEMENT)), true)) { - _nesting++; - return true; - } - if (to_check.compare(ENDIF_STATEMENT, 0, 0, int(strlen(ENDIF_STATEMENT)), true)) { - _nesting--; - return true; - } - return false; -} - -outcome write_build_config::output_decorated_macro(const astring &symbol_in, - const astring &value, astring &cfg_accumulator, astring &ver_accumulator) -{ - // make sure we catch any conditionals. - if (check_nesting(symbol_in)) - ADD_COMMENT_RETURN(symbol_in, value); - // toss out any exclusions. - if (exclusions().member(symbol_in)) - ADD_COMMENT_RETURN(symbol_in, value); - if (symbol_in.contains("\"") || value.contains("\"")) - ADD_COMMENT_RETURN(symbol_in, value); - if (symbol_in[0] == '[') - ADD_COMMENT_RETURN(symbol_in, value); - if (_nesting) - ADD_COMMENT_RETURN(symbol_in, value); - // switch the output stream based on whether its a version component or not. - astring *the_accumulator = &cfg_accumulator; - if (process_version_parts(symbol_in, value)) { - the_accumulator = &ver_accumulator; - } - // add a special tag so that we won't be colliding with other names. - astring symbol = astring("__build_") + symbol_in; - return output_macro(symbol, value, *the_accumulator); -} - -outcome write_build_config::output_definition_macro - (const astring &embedded_value, astring &accumulator) -{ -// FUNCDEF("output_definition_macro"); -//LOG(astring("into output def with: ") + embedded_value); - variable_tokenizer t; - t.parse(embedded_value); - if (!t.symbols()) - ADD_COMMENT_RETURN("bad definition", embedded_value); - if (exclusions().member(t.table().name(0))) - ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]); - if (_nesting) - ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]); - return output_macro(t.table().name(0), t.table()[0], accumulator); -} - -bool write_build_config::write_output_file(const astring &filename, - const astring &new_contents) -{ - FUNCDEF("write_output_file"); - // now read the soon-to-be output file so we can check its current state. - bool write_header = true; - byte_filer check_header(filename, "rb"); - if (check_header.good()) { - byte_array file_contents; - int read = check_header.read(file_contents, MAX_HEADER_FILE); -if (read < 1) LOG("why is existing header contentless?"); - if (read > 0) { - astring found(astring::UNTERMINATED, (char *)file_contents.observe(), - read); -//LOG(astring("got existing content:\n-----\n") + found + "\n-----\n"); -//LOG(astring("new_content has:\n-----\n") + new_contents + "\n-----\n"); - if (found == new_contents) { - write_header = false; - } - } - } - // writing only occurs when we know that the build configurations have - // changed. if the file is the same, we definitely don't want to write - // it because pretty much all the other files depend on it. - if (write_header) { - // we actually want to blast out a new file. - byte_filer build_header(filename, "wb"); - if (!build_header.good()) - non_continuable_error(static_class_name(), func, astring("failed to create " - "build header file in ") + build_header.filename()); - build_header.write(new_contents); - LOG(astring(static_class_name()) + ": wrote config to " - + build_header.filename()); - } else { - // nothing has changed. -// LOG(astring(static_class_name()) + ": config already up to date in " -// + filename); - } - return true; -} - -int write_build_config::execute() -{ - FUNCDEF("execute"); - SETUP_CONSOLE_LOGGER; // override the file_logger from app_shell. - - // find our build ini file. - astring repodir = environment::get("REPOSITORY_DIR"); - - // the below code should never be needed for a properly configured build. -#ifdef __WIN32__ - if (!repodir) repodir = "l:"; -#else // unix and other locations. - if (!repodir) - repodir = environment::get("HOME") + "/hoople"; -#endif - - astring fname; - // where we seek out our build settings. - astring parmfile = environment::get("BUILD_PARAMETER_FILE"); - if (parmfile.t()) fname = parmfile; - else fname = repodir + "/build.ini"; - - // find our storage area for the build headers. we know a couple build - // configurations by now, but this should really be coming out of a config - // file instead. - astring library_directory = repodir + "/source/lib_src/library"; - if (!filename(library_directory).good()) { - library_directory = repodir + "/source/core/library"; - if (!filename(library_directory).good()) { - library_directory = repodir + "/libraries/library"; - if (!filename(library_directory).good()) { - library_directory = repodir + "/../../libraries/library"; - if (!filename(library_directory).good()) { - non_continuable_error(static_class_name(), func, - astring("failed to locate the library folder storing the generated files.")); - } - } - } - } - - // these are very specific paths, but they really are where we expect to - // see the headers. - - astring cfg_header_filename = library_directory + "/" - "__build_configuration.h"; - astring ver_header_filename = library_directory + "/" - "__build_version.h"; - - // open the ini file for reading. - byte_filer ini(fname, "r"); - if (!ini.good()) - non_continuable_error(static_class_name(), func, astring("failed to open " - "build configuration file for reading at ") + ini.filename()); -//hmmm: parameterize the build ini thing above! - - // now we build strings that represents the output files we want to create. - astring cfg_accumulator; - astring ver_accumulator; - - // odd indentation below comes from the strings being c++ code that will be - // written to a file. -//hmmm: another location to fix!!! - cfg_accumulator += "\ -#ifndef BUILD_SYSTEM_CONFIGURATION\n\ -#define BUILD_SYSTEM_CONFIGURATION\n\n\ - // This file provides all of the code flags which were used when this build\n\ - // was generated. Some of the items in the build configuration have been\n\ - // stripped out because they are not used.\n\n"; - ver_accumulator += "\ -#ifndef BUILD_VERSION_CONFIGURATION\n\ -#define BUILD_VERSION_CONFIGURATION\n\n\ - // This file provides the version macros for this particular build.\n\n"; - - // iterate through the entries we read in earlier and generate our header. - astring symbol, value; - astring buffer; - while (!ini.eof()) { - int chars = ini.getline(buffer, MAX_LINE_SIZE); - if (!chars) continue; // hmmm. - - variable_tokenizer t; - t.parse(buffer); - if (!t.symbols()) continue; // not so good. - - // pull out the first pair we found and try to parse it. - symbol = t.table().name(0); - value = t.table()[0]; - symbol.strip_spaces(astring::FROM_BOTH_SIDES); - - // clean out + characters that can come from += declarations. - while ( (symbol[symbol.end()] == '+') || (symbol[symbol.end()] == ':') ) { - symbol.zap(symbol.end(), symbol.end()); - symbol.strip_spaces(astring::FROM_END); - } - - if (symbol[0] == '#') continue; // toss out any comments. - - if (!symbol) continue; // seems like that one didn't work out so well. - - if (symbol.compare(EXPORT_STATEMENT, 0, 0, int(strlen(EXPORT_STATEMENT)), true)) { - // clean out export statements in front of our variables. - symbol.zap(0, int(strlen(EXPORT_STATEMENT) - 1)); - } - - // check for a make-style macro definition. - if (symbol.compare(DEFINITIONS_STATEMENT, 0, 0, int(strlen(DEFINITIONS_STATEMENT)), true)) { - // found a macro definition. write that up after pulling the real - // contents out. - output_definition_macro(value, cfg_accumulator); - } else { - // this one is hopefully a very tasty specialized macro. we will - // show it with added text to make it unique. - output_decorated_macro(symbol, value, cfg_accumulator, ver_accumulator); - } - } - - // write some calculated macros now. - ver_accumulator += "\n"; - ver_accumulator += " // calculated macros are dropped in here.\n\n"; - - // we write our version in a couple forms. hopefully we accumulated it. - - // this one is the same as the file version currently (below), but may go to - // a different version at some point. - ver_accumulator += " #define __build_SYSTEM_VERSION \""; - ver_accumulator += _ver->flex_text_form(); - ver_accumulator += "\"\n\n"; - - // we drop in the version as numbers also, since the version RC wants that. - ver_accumulator += " #define __build_FILE_VERSION_COMMAS "; - ver_accumulator += _ver->flex_text_form(version::COMMAS); - ver_accumulator += "\n"; - // another form of the file version for dotted notation. - ver_accumulator += " #define __build_FILE_VERSION \""; - ver_accumulator += _ver->flex_text_form(); - ver_accumulator += "\"\n"; - - // product version is just the first two parts. - _ver->set_component(version::REVISION, "0"); - _ver->set_component(version::BUILD, "0"); - // product version as a list of numbers. - ver_accumulator += " #define __build_PRODUCT_VERSION_COMMAS "; - ver_accumulator += _ver->flex_text_form(version::COMMAS); - ver_accumulator += "\n"; - // another form of the product version for use as a string. - ver_accumulator += " #define __build_PRODUCT_VERSION \""; - ver_accumulator += _ver->flex_text_form(version::DOTS, version::MINOR); - ver_accumulator += "\"\n"; - - // write a blob of comments at the end with what we didn't use. - cfg_accumulator += "\n"; - cfg_accumulator += "/*\n"; - cfg_accumulator += "These settings were not used:\n"; - cfg_accumulator += *_end_matter; - cfg_accumulator += "*/\n"; - cfg_accumulator += "\n"; - cfg_accumulator += "#endif /* outer guard */\n\n"; - - // finish up the version file also. - ver_accumulator += "\n"; - ver_accumulator += "#endif /* outer guard */\n\n"; - - if (!write_output_file(cfg_header_filename, cfg_accumulator)) { - LOG(astring("failed writing output file ") + cfg_header_filename); - } - if (!write_output_file(ver_header_filename, ver_accumulator)) { - LOG(astring("failed writing output file ") + ver_header_filename); - } - - return 0; -} - -HOOPLE_MAIN(write_build_config, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/clam_tools/write_build_config.h b/core/tools/clam_tools/write_build_config.h deleted file mode 100644 index 3efa4b99..00000000 --- a/core/tools/clam_tools/write_build_config.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef BUILD_DEFAULTS_CLASS -#define BUILD_DEFAULTS_CLASS - -/*****************************************************************************\ -* * -* Name : write_build_config * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* This class creates a header file that will provide macros that govern * -* how the build is created under win32 using visual studio project files. * -* This file is not on other platforms, nor with the clam makefile system. * -* * -******************************************************************************* -* Copyright (c) 1995-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include - -class write_build_config : public application::application_shell -{ -public: - write_build_config(); - ~write_build_config(); - - DEFINE_CLASS_NAME("write_build_config"); - - int execute(); - //!< performs the main action of creating our build configuration header. - - const structures::string_set &exclusions(); - //!< returns the set of symbols that we will not include in the header. - - basis::outcome output_macro(const basis::astring &symbol, const basis::astring &value, - basis::astring &accumulator); - //!< sends a macro definition for "symbol" with "value" to "accumulator". - - basis::outcome output_decorated_macro(const basis::astring &symbol, const basis::astring &value, - basis::astring &cfg_accumulator, basis::astring &ver_accumulator); - //!< produces a new macro by adding a uniquifying string to "symbol". - /*!< if the item is a version component, it will be output to the - "ver_accumulator" but otherwise it goes to the "cfg_accumulator". */ - - basis::outcome output_definition_macro(const basis::astring &embedded_value, - basis::astring &accumulator); - //!< parses a 'name=value' pair out of "embedded_value" and writes a macro. - - bool process_version_parts(const basis::astring &symbol, const basis::astring &value); - //!< checks on "symbol" to see if it's a version component. stores if so. - /*!< if the string was a version component, then true is returned. */ - - bool check_nesting(const basis::astring &to_check); - //!< if "to_check" is a make conditional, the nesting level is adjusted. - /*!< also if it's a conditional, true is returned, which means the line - can be dropped. */ - - bool write_output_file(const basis::astring &filename, const basis::astring &contents); - //!< writes "contents" to "filename" if it differs from current contents. - -private: - basis::astring *_end_matter; // stuff that isn't part of the real file. - structures::version *_ver; // accumulated when we see the right parts. - int _nesting; // how many levels of conditionals do we see? - - // not provided. - write_build_config(const write_build_config &); - write_build_config &operator =(const write_build_config &); -}; - -#endif - diff --git a/core/tools/dependency_tool/Xfuncproto.h b/core/tools/dependency_tool/Xfuncproto.h deleted file mode 100644 index ffbbe905..00000000 --- a/core/tools/dependency_tool/Xfuncproto.h +++ /dev/null @@ -1,88 +0,0 @@ -/* $XConsortium: Xfuncproto.h,v 1.8 94/04/17 20:10:49 rws Exp $ */ -/* - * -Copyright (c) 1989, 1991 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - * - */ - -/* Definitions to make function prototypes manageable */ - -#ifndef _XFUNCPROTO_H_ -#define _XFUNCPROTO_H_ - -#ifndef NeedFunctionPrototypes -#if defined(FUNCPROTO) || __STDC__ || defined(__cplusplus) || defined(c_plusplus) -#define NeedFunctionPrototypes 1 -#else -#define NeedFunctionPrototypes 0 -#endif -#endif /* NeedFunctionPrototypes */ - -#ifndef NeedVarargsPrototypes -#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&2) -#define NeedVarargsPrototypes 1 -#else -#define NeedVarargsPrototypes 0 -#endif -#endif /* NeedVarargsPrototypes */ - -#if NeedFunctionPrototypes - -#ifndef NeedNestedPrototypes -#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&8) -#define NeedNestedPrototypes 1 -#else -#define NeedNestedPrototypes 0 -#endif -#endif /* NeedNestedPrototypes */ - -#ifndef _Xconst -#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&4) -#define _Xconst const -#else -#define _Xconst -#endif -#endif /* _Xconst */ - -#ifndef NeedWidePrototypes -#ifdef NARROWPROTO -#define NeedWidePrototypes 0 -#else -#define NeedWidePrototypes 1 /* default to make interropt. easier */ -#endif -#endif /* NeedWidePrototypes */ - -#endif /* NeedFunctionPrototypes */ - -#ifndef _XFUNCPROTOBEGIN -#ifdef __cplusplus /* for C++ V2.0 */ -#define _XFUNCPROTOBEGIN extern "C" { /* do not leave open across includes */ -#define _XFUNCPROTOEND } -#else -#define _XFUNCPROTOBEGIN -#define _XFUNCPROTOEND -#endif -#endif /* _XFUNCPROTOBEGIN */ - -#endif /* _XFUNCPROTO_H_ */ diff --git a/core/tools/dependency_tool/Xos2defs.h b/core/tools/dependency_tool/Xos2defs.h deleted file mode 100644 index 6e4b1324..00000000 --- a/core/tools/dependency_tool/Xos2defs.h +++ /dev/null @@ -1,74 +0,0 @@ -/* $XConsortium: Xw32defs.h,v 1.4 94/02/25 18:50:11 rws Exp $ */ - -#error - -#ifndef _XW32DEFS_H -#define _XW32DEFS_H - -typedef char *caddr_t; - -#define access _access -#define chdir _chdir -#define chmod _chmod -#define close _close -#define creat _creat -#define dup _dup -#define dup2 _dup2 -#define environ _environ -#define execl _execl -#define execle _execle -#define execlp _execlp -#define execlpe _execlpe -#define execv _execv -#define execve _execve -#define execvp _execvp -#define execvpe _execvpe -#define fdopen _fdopen -#define fileno _fileno -#define fstat _fstat -#define getcwd _getcwd -#define getpid _getpid -#define hypot _hypot -#define isatty _isatty -#define lseek _lseek -#define mkdir _mkdir -#define mktemp _mktemp -#define open _open -#define putenv _putenv -#define read _read -#define rmdir _rmdir -#define sleep(x) _sleep((x) * 1000) -#define stat _stat -#define sys_errlist _sys_errlist -#define sys_nerr _sys_nerr -#define umask _umask -#define unlink _unlink -#define write _write - -/* -#define O_RDONLY _O_RDONLY -#define O_WRONLY _O_WRONLY -#define O_RDWR _O_RDWR -#define O_APPEND _O_APPEND -#define O_CREAT _O_CREAT -#define O_TRUNC _O_TRUNC -#define O_EXCL _O_EXCL -#define O_TEXT _O_TEXT -#define O_BINARY _O_BINARY -#define O_RAW _O_BINARY - -#define S_IFMT _S_IFMT -#define S_IFDIR _S_IFDIR -#define S_IFCHR _S_IFCHR -#define S_IFREG _S_IFREG -#define S_IREAD _S_IREAD -#define S_IWRITE _S_IWRITE -#define S_IEXEC _S_IEXEC - -*/ -#define F_OK 0 -#define X_OK 1 -#define W_OK 2 -#define R_OK 4 - -#endif diff --git a/core/tools/dependency_tool/Xosdefs.h b/core/tools/dependency_tool/Xosdefs.h deleted file mode 100644 index 4513bd90..00000000 --- a/core/tools/dependency_tool/Xosdefs.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * O/S-dependent (mis)feature macro definitions - * - * $XConsortium: Xosdefs.h,v 1.14 94/11/30 20:48:05 kaleb Exp $ - * $XFree86: xc/include/Xosdefs.h,v 3.7 1995/01/28 15:42:05 dawes Exp $ - * -Copyright (c) 1991 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - */ - -#ifndef _XOSDEFS_H_ -#define _XOSDEFS_H_ - -/* - * X_NOT_STDC_ENV means does not have ANSI C header files. Lack of this - * symbol does NOT mean that the system has stdarg.h. - * - * X_NOT_POSIX means does not have POSIX header files. Lack of this - * symbol does NOT mean that the POSIX environment is the default. - * You may still have to define _POSIX_SOURCE to get it. - */ - -#ifdef NOSTDHDRS -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif - -#ifdef sony -#if !defined(SYSTYPE_SYSV) && !defined(_SYSTYPE_SYSV) -#define X_NOT_POSIX -#endif -#endif - -#ifdef UTEK -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif - -#ifdef vax -#ifndef ultrix /* assume vanilla BSD */ -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif -#endif - -#ifdef luna -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif - -#ifdef Mips -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif - -#ifdef USL -#ifdef SYSV /* (release 3.2) */ -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif -#endif - -#ifdef i386 -#ifdef SYSV -#ifndef SCO -#define X_NOT_POSIX -#endif -#define X_NOT_STDC_ENV -#endif -#endif - -#ifdef MOTOROLA -#ifdef SYSV -#define X_NOT_STDC_ENV -#endif -#endif - -#ifdef sun -#ifdef SVR4 -/* define this to whatever it needs to be */ -#define X_POSIX_C_SOURCE 199300L -#endif -#endif - -#ifdef WIN32 -#ifndef _POSIX_ -#define X_NOT_POSIX -#endif -#endif - -#ifdef __OS2__ -#ifndef _POSIX_ -#define X_NOT_POSIX -#endif -#endif - -#if defined(nec_ews_svr2) || defined(SX) || defined(PC_UX) -#define X_NOT_POSIX -#define X_NOT_STDC_ENV -#endif - -#ifdef __EMX__ -#define USGISH -#endif - -#endif /* _XOSDEFS_H_ */ diff --git a/core/tools/dependency_tool/Xw32defs.h b/core/tools/dependency_tool/Xw32defs.h deleted file mode 100644 index 58c2187c..00000000 --- a/core/tools/dependency_tool/Xw32defs.h +++ /dev/null @@ -1,72 +0,0 @@ -/* $XConsortium: Xw32defs.h,v 1.4 94/02/25 18:50:11 rws Exp $ */ - -#ifndef _XW32DEFS_H -#define _XW32DEFS_H - -typedef char *caddr_t; - -#define access _access -#define alloca _alloca -#define chdir _chdir -#define chmod _chmod -#define close _close -#define creat _creat -#define dup _dup -#define dup2 _dup2 -#define environ _environ -#define execl _execl -#define execle _execle -#define execlp _execlp -#define execlpe _execlpe -#define execv _execv -#define execve _execve -#define execvp _execvp -#define execvpe _execvpe -#define fdopen _fdopen -#define fileno _fileno -#define fstat _fstat -#define getcwd _getcwd -#define getpid _getpid -#define hypot _hypot -#define isascii __isascii -#define isatty _isatty -#define lseek _lseek -#define mkdir _mkdir -#define mktemp _mktemp -#define open _open -#define putenv _putenv -#define read _read -#define rmdir _rmdir -#define sleep(x) _sleep((x) * 1000) -#define stat _stat -#define sys_errlist _sys_errlist -#define sys_nerr _sys_nerr -#define umask _umask -#define unlink _unlink -#define write _write - -#define O_RDONLY _O_RDONLY -#define O_WRONLY _O_WRONLY -#define O_RDWR _O_RDWR -#define O_APPEND _O_APPEND -#define O_CREAT _O_CREAT -#define O_TRUNC _O_TRUNC -#define O_EXCL _O_EXCL -#define O_TEXT _O_TEXT -#define O_BINARY _O_BINARY -#define O_RAW _O_BINARY - -#define S_IFMT _S_IFMT -#define S_IFDIR _S_IFDIR -#define S_IFCHR _S_IFCHR -#define S_IFREG _S_IFREG -#define S_IREAD _S_IREAD -#define S_IWRITE _S_IWRITE -#define S_IEXEC _S_IEXEC - -#define F_OK 0 -#define X_OK 1 -#define W_OK 2 -#define R_OK 4 - -#endif diff --git a/core/tools/dependency_tool/cppsetup.cpp b/core/tools/dependency_tool/cppsetup.cpp deleted file mode 100644 index dc404113..00000000 --- a/core/tools/dependency_tool/cppsetup.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* $XConsortium: cppsetup.c,v 1.13 94/04/17 20:10:32 gildea Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "def.h" - -#ifdef CPP - -#include "ifparser.h" - -/* - * This file is strictly for the sake of cpy.y and yylex.c (if - * you indeed have the source for cpp). - */ -#define IB 1 -#define SB 2 -#define NB 4 -#define CB 8 -#define QB 16 -#define WB 32 -#define SALT '#' -#if pdp11 | vax | ns16000 | mc68000 | ibm032 -#define COFF 128 -#else -#define COFF 0 -#endif -/* - * These variables used by cpy.y and yylex.c - */ -extern char *outp, *inp, *newp, *pend; -extern char *ptrtab; -extern char fastab[]; -extern char slotab[]; - -/* - * cppsetup - */ -struct filepointer *currentfile; -inclist *currentinc; - -int cppsetup(register char *line, register struct filepointer *filep, - register inclist *inc) -{ - register char *p, savec; - static bool setupdone = false; - bool value; - - if (!setupdone) { - cpp_varsetup(); - setupdone = true; - } - - currentfile = filep; - currentinc = inc; - inp = newp = line; - for (p=newp; *p; p++) - ; - - /* - * put a newline back on the end, and set up pend, etc. - */ - *p++ = '\n'; - savec = *p; - *p = '\0'; - pend = p; - - ptrtab = slotab+COFF; - *--inp = SALT; - outp=inp; - value = yyparse(); - *p = savec; - return(value); -} - -struct symtab *lookup(char *symbol) -{ - static struct symtab undefined; - struct symtab *sp; - - sp = isdefined(symbol, currentinc, NULL); - if (sp == NULL) { - sp = &undefined; - sp->s_value = NULL; - } - return (sp); -} - -int pperror(int tag, int x0, int x1, int x2, int x3, int x4) -{ - warning("\"%s\", line %d: ", currentinc->i_file, currentfile->f_line); - warning(x0,x1,x2,x3,x4); -} - - -int yyerror(register char *s) -{ - fatalerr("Fatal error: %s\n", s); -} - -#else /* not CPP */ - -#include "ifparser.h" - -#include - -struct _parse_data { - struct filepointer *filep; - inclist *inc; - const char *line; -}; - -static const char *_my_if_errors(IfParser *ip, const char *cp, - const char *expecting) -{ - struct _parse_data *pd = (struct _parse_data *) ip->data; - int lineno = pd->filep->f_line; - char *filename = pd->inc->i_file; - char prefix[300]; - int prefixlen; - int i; - - sprintf (prefix, "\"%s\":%d", filename, lineno); - prefixlen = int(strlen(prefix)); - fprintf (stderr, "%s: %s", prefix, pd->line); - i = int(cp - pd->line); - if (i > 0 && pd->line[i-1] != '\n') { - putc ('\n', stderr); - } - for (i += prefixlen + 3; i > 0; i--) { - putc (' ', stderr); - } - fprintf (stderr, "^--- expecting %s\n", expecting); - return NULL; -} - - -#define MAXNAMELEN 256 - -static struct symtab *_lookup_variable(IfParser *ip, const char *var, int len) -{ - char tmpbuf[MAXNAMELEN + 1]; - struct _parse_data *pd = (struct _parse_data *) ip->data; - - if (len > MAXNAMELEN) - return 0; - - strncpy (tmpbuf, var, len); - tmpbuf[len] = '\0'; - return isdefined (tmpbuf, pd->inc, NULL); -} - - -static int _my_eval_defined(IfParser *ip, const char *var, int len) -{ - if (_lookup_variable (ip, var, len)) return 1; - else return 0; -} - -#define isvarfirstletter(ccc) (isalpha(ccc) || (ccc) == '_') - -static int _my_eval_variable(IfParser *ip, const char *var, int len) -{ - struct symtab *s; - - s = _lookup_variable (ip, var, len); - if (!s) - return 0; - do { - var = s->s_value; - if (!isvarfirstletter(*var)) - break; - s = _lookup_variable (ip, var, int(strlen(var))); - } while (s); - - return atoi(var); -} - - -int cppsetup(register char *line, register struct filepointer *filep, - register inclist *inc) -{ - IfParser ip; - struct _parse_data pd; - int val = 0; - - pd.filep = filep; - pd.inc = inc; - pd.line = line; - ip.funcs.handle_error = _my_if_errors; - ip.funcs.eval_defined = _my_eval_defined; - ip.funcs.eval_variable = _my_eval_variable; - ip.data = (char *) &pd; - - (void) ParseIfExpression (&ip, line, &val); - if (val) - return IF; - else - return IFFALSE; -} - -#endif /* CPP */ - diff --git a/core/tools/dependency_tool/def.h b/core/tools/dependency_tool/def.h deleted file mode 100644 index de4e4189..00000000 --- a/core/tools/dependency_tool/def.h +++ /dev/null @@ -1,195 +0,0 @@ -/* $XConsortium: def.h,v 1.25 94/04/17 20:10:33 gildea Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#include -#include "Xosdefs.h" -#ifdef WIN32 -#endif -#ifdef __OS2__ -#define __STDC__ 1 -#include "Xos2defs.h" -#endif -#include "Xfuncproto.h" -#include -#ifndef X_NOT_POSIX -#ifndef _POSIX_SOURCE -#define _POSIX_SOURCE -#endif -#endif -#include -#include -#include - -#define MAXDEFINES 512 -#define MAXFILES 4000 -#define MAXDIRS 640 -#define SYMTABINC 10 /* must be > 1 for define() to work right */ - -/* the following must match the directives table in main.c */ -#define IF 0 -#define IFDEF 1 -#define IFNDEF 2 -#define ELSE 3 -#define ENDIF 4 -#define DEFINE 5 -#define UNDEF 6 -#define INCLUDE 7 -#define LINE 8 -#define PRAGMA 9 -#define ERROR 10 -#define IDENT 11 -#define SCCS 12 -#define ELIF 13 -#define EJECT 14 -#define IMPORT 15 - -#define IFFALSE 16 /* pseudo value --- never matched */ -#define ELIFFALSE 17 /* pseudo value --- never matched */ -#define INCLUDEDOT 18 /* pseudo value --- never matched */ -#define IFGUESSFALSE 19 /* pseudo value --- never matched */ -#define ELIFGUESSFALSE 20 /* pseudo value --- never matched */ - -///#define DEBUG - // uncomment this for debugging version. - -#ifdef DEBUG -extern int _debugmask; -/* - * debug levels are: - * - * 0 show ifn*(def)*,endif - * 1 trace defined/!defined - * 2 show #include - * 3 show #include SYMBOL - * 4-6 unused - */ -#define debug(level,arg) { if (_debugmask & (1 << level)) warning arg; } -#else -#define debug(level,arg) {} -#endif /* DEBUG */ - -struct symtab { - char *s_name; - char *s_value; -}; - -struct inclist { - char *i_incstring; /* string from #include line */ - char *i_file; /* path name of the include file */ - inclist **i_list; /* list of files it itself includes */ - int i_listlen; /* length of i_list */ - symtab *i_defs; /* symbol table for this file */ - int i_ndefs; /* current # defines */ - int i_deflen; /* amount of space in table */ - bool i_defchecked; /* whether defines have been checked */ - bool i_notified; /* whether we have revealed includes */ - bool i_marked; /* whether it's in the makefile */ - bool i_searched; /* whether we have read this */ - bool i_included_sym; /* whether #include SYMBOL was found */ - /* Can't use i_list if true */ -}; - -struct filepointer { - char *f_p; - char *f_base; - char *f_end; - long f_len; - long f_line; - char *f_name; -}; - -#ifndef X_NOT_STDC_ENV -#include -#if defined(macII) && !defined(__STDC__) /* stdlib.h fails to define these */ -char *malloc(), *realloc(); -#endif /* macII */ -#else -char *malloc(); -char *realloc(); -#endif - -/* -char *getline(); -symtab *slookup(); -symtab *isdefined(); -symtab *fdefined(); -filepointer *getfile(); -inclist *newinclude(register char *, register char *); -inclist *inc_path(); -*/ - -// cppsetup.cpp: -int cppsetup(register char *line, register filepointer *filep, - register inclist *inc); - -// include.cpp -inclist *newinclude(register char *newfile, register char *incstring); -void inc_clean(); -inclist *inc_path(register char *file, register char *include, bool dot, - bool &failure_okay); -void included_by(register inclist *ip, register inclist *newfile); - -// main.cpp: -char *base_name(register char *file); -char *copy(register char *str); -filepointer *getfile(char *file); -void freefile(filepointer *fp); -char *getline(register filepointer *filep); -int match(register const char *str, register const char **list); -void redirect(char *line, char *makefile); -#if NeedVarargsPrototypes - void fatalerr(const char *, ...); - void warning(const char *, ...); - void warning1(const char *, ...); -#endif - -// parse.cpp: -void define(char *def, inclist *file); -void define2(char *name, char *val, inclist *file); -int deftype(register char *line, register filepointer *filep, - register inclist *file_red, register inclist *file, - int parse_it); -symtab *fdefined(register char *symbol, inclist *file, inclist **srcfile); -int find_includes(filepointer *filep, inclist *file, - inclist *file_red, int recursion, bool failOK); -int gobble(register filepointer *filep, inclist *file, - inclist *file_red); -symtab *isdefined(register char *symbol, inclist *file, - inclist **srcfile); -symtab *slookup(register char *symbol, register inclist *file); -void undefine(char *symbol, register inclist *file); -int zero_value(register char *exp, register filepointer *filep, - register inclist *file_red); - -// pr.cpp: -void add_include(filepointer *filep, inclist *file, - inclist *file_red, char *include, bool dot, bool failOK); -void pr(register inclist *ip, char *file, char *base, bool rc_file); -void recursive_pr_include(register inclist *head, register char *file, - register char *base); - diff --git a/core/tools/dependency_tool/ifparser.cpp b/core/tools/dependency_tool/ifparser.cpp deleted file mode 100644 index b4de6824..00000000 --- a/core/tools/dependency_tool/ifparser.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* - * $XConsortium: ifparser.c,v 1.7 94/01/18 21:30:50 rws Exp $ - * - * Copyright 1992 Network Computing Devices, Inc. - * - * Permission to use, copy, modify, and distribute this software and its - * documentation for any purpose and without fee is hereby granted, provided - * that the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation, and that the name of Network Computing Devices may not be - * used in advertising or publicity pertaining to distribution of the software - * without specific, written prior permission. Network Computing Devices makes - * no representations about the suitability of this software for any purpose. - * It is provided ``as is'' without express or implied warranty. - * - * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS - * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, - * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL, - * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - * - * Author: Jim Fulton - * Network Computing Devices, Inc. - * - * Simple if statement processor - * - * This module can be used to evaluate string representations of C language - * if constructs. It accepts the following grammar: - * - * EXPRESSION := VALUE - * | VALUE BINOP EXPRESSION - * - * VALUE := '(' EXPRESSION ')' - * | '!' VALUE - * | '-' VALUE - * | 'defined' '(' variable ')' - * | 'defined' variable - * | # variable '(' variable-list ')' - * | variable - * | number - * - * BINOP := '*' | '/' | '%' - * | '+' | '-' - * | '<<' | '>>' - * | '<' | '>' | '<=' | '>=' - * | '==' | '!=' - * | '&' | '|' - * | '&&' | '||' - * - * The normal C order of precidence is supported. - * - * - * External Entry Points: - * - * ParseIfExpression parse a string for #if - */ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "ifparser.h" - -#include -#include -#include - -/**************************************************************************** - Internal Macros and Utilities for Parser - ****************************************************************************/ - -#define DO(val) if (!(val)) return NULL -#define CALLFUNC(ggg,fff) (*((ggg)->funcs.fff)) -#define SKIPSPACE(ccc) while (isspace(*ccc)) ccc++ -#define isvarfirstletter(ccc) (isalpha(ccc) || (ccc) == '_') - - -static const char *parse_variable(IfParser *g, const char *cp, - const char **varp) -{ - SKIPSPACE (cp); - - if (!isvarfirstletter (*cp)) - return CALLFUNC(g, handle_error) (g, cp, "variable name"); - - *varp = cp; - /* EMPTY */ - for (cp++; isalnum(*cp) || *cp == '_'; cp++) ; - return cp; -} - - -static const char *parse_number(IfParser *g, const char *cp, int *valp) -{ - SKIPSPACE (cp); - - if (!isdigit(*cp)) - return CALLFUNC(g, handle_error) (g, cp, "number"); - -#ifdef WIN32 - char *hold_result; - *valp = strtol(cp, &hold_result, 0); - cp = hold_result; -#else - *valp = atoi (cp); - /* EMPTY */ - for (cp++; isdigit(*cp); cp++) ; -#endif - return cp; -} - - -static const char *parse_value(IfParser *g, const char *cp, int *valp) -{ - const char *var; - - *valp = 0; - - SKIPSPACE (cp); - if (!*cp) - return cp; - - switch (*cp) { - case '(': - DO (cp = ParseIfExpression (g, cp + 1, valp)); - SKIPSPACE (cp); - if (*cp != ')') - return CALLFUNC(g, handle_error) (g, cp, ")"); - - return cp + 1; /* skip the right paren */ - - case '!': - DO (cp = parse_value (g, cp + 1, valp)); - *valp = !(*valp); - return cp; - - case '-': - DO (cp = parse_value (g, cp + 1, valp)); - *valp = -(*valp); - return cp; - - case '#': - DO (cp = parse_variable (g, cp + 1, &var)); - SKIPSPACE (cp); - if (*cp != '(') - return CALLFUNC(g, handle_error) (g, cp, "("); - do { - DO (cp = parse_variable (g, cp + 1, &var)); - SKIPSPACE (cp); - } while (*cp && *cp != ')'); - if (*cp != ')') - return CALLFUNC(g, handle_error) (g, cp, ")"); - *valp = 1; /* XXX */ - return cp + 1; - - case 'd': - if (strncmp (cp, "defined", 7) == 0 && !isalnum(cp[7])) { - int paren = 0; - cp += 7; - SKIPSPACE (cp); - if (*cp == '(') { - paren = 1; - cp++; - } - DO (cp = parse_variable (g, cp, &var)); - SKIPSPACE (cp); - if (paren && *cp != ')') - return CALLFUNC(g, handle_error) (g, cp, ")"); - *valp = (*(g->funcs.eval_defined)) (g, var, int(cp - var)); - return cp + paren; /* skip the right paren */ - } - /* fall out */ - } - - if (isdigit(*cp)) { - DO (cp = parse_number (g, cp, valp)); - } else if (!isvarfirstletter(*cp)) - return CALLFUNC(g, handle_error) (g, cp, "variable or number"); - else { - DO (cp = parse_variable (g, cp, &var)); - *valp = (*(g->funcs.eval_variable)) (g, var, int(cp - var)); - } - - return cp; -} - -static const char *parse_product(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_value (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '*': - DO (cp = parse_product (g, cp + 1, &rightval)); - *valp = (*valp * rightval); - break; - - case '/': - DO (cp = parse_product (g, cp + 1, &rightval)); - *valp = (*valp / rightval); - break; - - case '%': - DO (cp = parse_product (g, cp + 1, &rightval)); - *valp = (*valp % rightval); - break; - } - return cp; -} - -static const char *parse_sum(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_product (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '+': - DO (cp = parse_sum (g, cp + 1, &rightval)); - *valp = (*valp + rightval); - break; - - case '-': - DO (cp = parse_sum (g, cp + 1, &rightval)); - *valp = (*valp - rightval); - break; - } - return cp; -} - - -static const char *parse_shift(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_sum (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '<': - if (cp[1] == '<') { - DO (cp = parse_shift (g, cp + 2, &rightval)); - *valp = (*valp << rightval); - } - break; - - case '>': - if (cp[1] == '>') { - DO (cp = parse_shift (g, cp + 2, &rightval)); - *valp = (*valp >> rightval); - } - break; - } - return cp; -} - -static const char *parse_inequality(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_shift (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '<': - if (cp[1] == '=') { - DO (cp = parse_inequality (g, cp + 2, &rightval)); - *valp = (*valp <= rightval); - } else { - DO (cp = parse_inequality (g, cp + 1, &rightval)); - *valp = (*valp < rightval); - } - break; - - case '>': - if (cp[1] == '=') { - DO (cp = parse_inequality (g, cp + 2, &rightval)); - *valp = (*valp >= rightval); - } else { - DO (cp = parse_inequality (g, cp + 1, &rightval)); - *valp = (*valp > rightval); - } - break; - } - return cp; -} - -static const char *parse_equality(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_inequality (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '=': - if (cp[1] == '=') - cp++; - DO (cp = parse_equality (g, cp + 1, &rightval)); - *valp = (*valp == rightval); - break; - - case '!': - if (cp[1] != '=') - break; - DO (cp = parse_equality (g, cp + 2, &rightval)); - *valp = (*valp != rightval); - break; - } - return cp; -} - -static const char *parse_band(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_equality (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '&': - if (cp[1] != '&') { - DO (cp = parse_band (g, cp + 1, &rightval)); - *valp = (*valp & rightval); - } - break; - } - return cp; -} - - -static const char *parse_bor(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_band (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '|': - if (cp[1] != '|') { - DO (cp = parse_bor (g, cp + 1, &rightval)); - *valp = (*valp | rightval); - } - break; - } - return cp; -} - -static const char *parse_land(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_bor (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '&': - if (cp[1] != '&') - return CALLFUNC(g, handle_error) (g, cp, "&&"); - DO (cp = parse_land (g, cp + 2, &rightval)); - *valp = (*valp && rightval); - break; - } - return cp; -} - -static const char *parse_lor(IfParser *g, const char *cp, int *valp) -{ - int rightval; - - DO (cp = parse_land (g, cp, valp)); - SKIPSPACE (cp); - - switch (*cp) { - case '|': - if (cp[1] != '|') - return CALLFUNC(g, handle_error) (g, cp, "||"); - DO (cp = parse_lor (g, cp + 2, &rightval)); - *valp = (*valp || rightval); - break; - } - return cp; -} - - -/**************************************************************************** - External Entry Points - ****************************************************************************/ - -const char *ParseIfExpression(IfParser *g, const char *cp, int *valp) -{ - return parse_lor (g, cp, valp); -} - - diff --git a/core/tools/dependency_tool/ifparser.h b/core/tools/dependency_tool/ifparser.h deleted file mode 100644 index 72fb6a47..00000000 --- a/core/tools/dependency_tool/ifparser.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * $XConsortium: ifparser.h,v 1.1 92/08/22 13:05:39 rws Exp $ - * - * Copyright 1992 Network Computing Devices, Inc. - * - * Permission to use, copy, modify, and distribute this software and its - * documentation for any purpose and without fee is hereby granted, provided - * that the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation, and that the name of Network Computing Devices may not be - * used in advertising or publicity pertaining to distribution of the software - * without specific, written prior permission. Network Computing Devices makes - * no representations about the suitability of this software for any purpose. - * It is provided ``as is'' without express or implied warranty. - * - * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS - * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, - * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL, - * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - * - * Author: Jim Fulton - * Network Computing Devices, Inc. - * - * Simple if statement processor - * - * This module can be used to evaluate string representations of C language - * if constructs. It accepts the following grammar: - * - * EXPRESSION := VALUE - * | VALUE BINOP EXPRESSION - * - * VALUE := '(' EXPRESSION ')' - * | '!' VALUE - * | '-' VALUE - * | 'defined' '(' variable ')' - * | variable - * | number - * - * BINOP := '*' | '/' | '%' - * | '+' | '-' - * | '<<' | '>>' - * | '<' | '>' | '<=' | '>=' - * | '==' | '!=' - * | '&' | '|' - * | '&&' | '||' - * - * The normal C order of precidence is supported. - * - * - * External Entry Points: - * - * ParseIfExpression parse a string for #if - */ - -#include - -struct IfParser { - struct { /* functions */ - const char *(*handle_error) (IfParser *, const char *, const char *); - int (*eval_variable) (IfParser *, const char *, int); - int (*eval_defined) (IfParser *, const char *, int); - } funcs; - char *data; -}; - -const char *ParseIfExpression(IfParser *, const char *, int *); - diff --git a/core/tools/dependency_tool/imakemdep.h b/core/tools/dependency_tool/imakemdep.h deleted file mode 100644 index d9d2c277..00000000 --- a/core/tools/dependency_tool/imakemdep.h +++ /dev/null @@ -1,718 +0,0 @@ -/* $XConsortium: imakemdep.h,v 1.82 95/01/12 16:27:01 kaleb Exp $ */ -/* $XFree86: xc/config/imake/imakemdep.h,v 3.8 1995/01/28 15:40:59 dawes Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - - -/* - * This file contains machine-dependent constants for the imake utility. - * When porting imake, read each of the steps below and add in any necessary - * definitions. In general you should *not* edit ccimake.c or imake.c! - */ - -#ifdef CCIMAKE -/* - * Step 1: imake_ccflags - * Define any special flags that will be needed to get imake.c to compile. - * These will be passed to the compile along with the contents of the - * make variable BOOTSTRAPCFLAGS. - */ -#ifdef hpux -#ifdef hp9000s800 -#define imake_ccflags "-DSYSV" -#else -#define imake_ccflags "-Wc,-Nd4000,-Ns3000 -DSYSV" -#endif -#endif - -#if defined(macII) || defined(_AUX_SOURCE) -#define imake_ccflags "-DmacII -DSYSV" -#endif - -#ifdef stellar -#define imake_ccflags "-DSYSV" -#endif - -#if defined(USL) || defined(Oki) || defined(NCR) -#define imake_ccflags "-Xc -DSVR4" -#endif - -#ifdef sony -#if defined(SYSTYPE_SYSV) || defined(_SYSTYPE_SYSV) -#define imake_ccflags "-DSVR4" -#else -#include -#if NEWSOS < 41 -#define imake_ccflags "-Dbsd43 -DNOSTDHDRS" -#else -#if NEWSOS < 42 -#define imake_ccflags "-Dbsd43" -#endif -#endif -#endif -#endif - -#ifdef _CRAY -#define imake_ccflags "-DSYSV -DUSG" -#endif - -#if defined(_IBMR2) || defined(aix) -#define imake_ccflags "-Daix -DSYSV" -#endif - -#ifdef Mips -# if defined(SYSTYPE_BSD) || defined(BSD) || defined(BSD43) -# define imake_ccflags "-DBSD43" -# else -# define imake_ccflags "-DSYSV" -# endif -#endif - -#ifdef is68k -#define imake_ccflags "-Dluna -Duniosb" -#endif - -#ifdef SYSV386 -# ifdef SVR4 -# define imake_ccflags "-Xc -DSVR4" -# else -# define imake_ccflags "-DSYSV" -# endif -#endif - -#ifdef SVR4 -# ifdef i386 -# define imake_ccflags "-Xc -DSVR4" -# endif -#endif - -#ifdef SYSV -# ifdef i386 -# define imake_ccflags "-DSYSV" -# endif -#endif - -#ifdef __convex__ -#define imake_ccflags "-fn -tm c1" -#endif - -#ifdef apollo -#define imake_ccflags "-DX_NOT_POSIX" -#endif - -#ifdef WIN32 -#define imake_ccflags "-nologo -batch -D__STDC__" -#endif - -#ifdef __uxp__ -#define imake_ccflags "-DSVR4 -DANSICPP" -#endif - -#ifdef __sxg__ -#define imake_ccflags "-DSYSV -DUSG -DNOSTDHDRS" -#endif - -#ifdef sequent -#define imake_ccflags "-DX_NOT_STDC_ENV -DX_NOT_POSIX" -#endif - -#ifdef _SEQUENT_ -#define imake_ccflags "-DSYSV -DUSG" -#endif - -#if defined(SX) || defined(PC_UX) -#define imake_ccflags "-DSYSV" -#endif - -#ifdef nec_ews_svr2 -#define imake_ccflags "-DUSG" -#endif - -#if defined(nec_ews_svr4) || defined(_nec_ews_svr4) || defined(_nec_up) || defined(_nec_ft) -#define imake_ccflags "-DSVR4" -#endif - -#ifdef MACH -#define imake_ccflags "-DNOSTDHDRS" -#endif - -/* this is for OS/2 under EMX. This won't work with DOS */ -#if defined(__EMX__) -#define imake_ccflags "-DBSD43" -#endif - -#else /* not CCIMAKE */ -#ifndef MAKEDEPEND -/* - * Step 2: dup2 - * If your OS doesn't have a dup2() system call to duplicate one file - * descriptor onto another, define such a mechanism here (if you don't - * already fall under the existing category(ies). - */ -#if defined(SYSV) && !defined(_CRAY) && !defined(Mips) && !defined(_SEQUENT_) -#define dup2(fd1,fd2) ((fd1 == fd2) ? fd1 : (close(fd2), \ - fcntl(fd1, F_DUPFD, fd2))) -#endif - - -/* - * Step 3: FIXUP_CPP_WHITESPACE - * If your cpp collapses tabs macro expansions into a single space and - * replaces escaped newlines with a space, define this symbol. This will - * cause imake to attempt to patch up the generated Makefile by looking - * for lines that have colons in them (this is why the rules file escapes - * all colons). One way to tell if you need this is to see whether or not - * your Makefiles have no tabs in them and lots of @@ strings. - */ -#if defined(sun) || defined(SYSV) || defined(SVR4) || defined(hcx) || defined(WIN32) || (defined(AMOEBA) && defined(CROSS_COMPILE)) -#define FIXUP_CPP_WHITESPACE -#endif -#ifdef WIN32 -#define REMOVE_CPP_LEADSPACE -#define INLINE_SYNTAX -#define MAGIC_MAKE_VARS -#endif -#ifdef __minix_vmd -#define FIXUP_CPP_WHITESPACE -#endif - -/* - * Step 4: USE_CC_E, DEFAULT_CC, DEFAULT_CPP - * If you want to use cc -E instead of cpp, define USE_CC_E. - * If use cc -E but want a different compiler, define DEFAULT_CC. - * If the cpp you need is not in /lib/cpp, define DEFAULT_CPP. - */ -#ifdef hpux -#define USE_CC_E -#endif -#ifdef WIN32 -#define USE_CC_E -#define DEFAULT_CC "cl" -#endif -#ifdef apollo -#define DEFAULT_CPP "/usr/lib/cpp" -#endif -#if defined(_IBMR2) && !defined(DEFAULT_CPP) -#define DEFAULT_CPP "/usr/lpp/X11/Xamples/util/cpp/cpp" -#endif -#if defined(sun) && defined(SVR4) -#define DEFAULT_CPP "/usr/ccs/lib/cpp" -#endif -#ifdef __bsdi__ -#define DEFAULT_CPP "/usr/bin/cpp" -#endif -#ifdef __uxp__ -#define DEFAULT_CPP "/usr/ccs/lib/cpp" -#endif -#ifdef __sxg__ -#define DEFAULT_CPP "/usr/lib/cpp" -#endif -#ifdef _CRAY -#define DEFAULT_CPP "/lib/pcpp" -#endif -#if defined(__386BSD__) || defined(__NetBSD__) || defined(__FreeBSD__) -#define DEFAULT_CPP "/usr/libexec/cpp" -#endif -#ifdef MACH -#define USE_CC_E -#endif -#ifdef __minix_vmd -#define DEFAULT_CPP "/usr/lib/cpp" -#endif -#if defined(__EMX__) -/* expects cpp in PATH */ -#define DEFAULT_CPP "cpp" -#endif - -/* - * Step 5: cpp_argv - * The following table contains the flags that should be passed - * whenever a Makefile is being generated. If your preprocessor - * doesn't predefine any unique symbols, choose one and add it to the - * end of this table. Then, do the following: - * - * a. Use this symbol in Imake.tmpl when setting MacroFile. - * b. Put this symbol in the definition of BootstrapCFlags in your - * .cf file. - * c. When doing a make World, always add "BOOTSTRAPCFLAGS=-Dsymbol" - * to the end of the command line. - * - * Note that you may define more than one symbol (useful for platforms - * that support multiple operating systems). - */ - -#define ARGUMENTS 50 /* number of arguments in various arrays */ -char *cpp_argv[ARGUMENTS] = { - "cc", /* replaced by the actual program to exec */ - "-I.", /* add current directory to include path */ -#ifdef unix - "-Uunix", /* remove unix symbol so that filename unix.c okay */ -#endif -#if defined(__386BSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(MACH) -# ifdef __i386__ - "-D__i386__", -# endif -# ifdef __GNUC__ - "-traditional" -# endif -#endif -#ifdef M4330 - "-DM4330", /* Tektronix */ -#endif -#ifdef M4310 - "-DM4310", /* Tektronix */ -#endif -#if defined(macII) || defined(_AUX_SOURCE) - "-DmacII", /* Apple A/UX */ -#endif -#ifdef USL - "-DUSL", /* USL */ -#endif -#ifdef sony - "-Dsony", /* Sony */ -#if !defined(SYSTYPE_SYSV) && !defined(_SYSTYPE_SYSV) && NEWSOS < 42 - "-Dbsd43", -#endif -#endif -#ifdef _IBMR2 - "-D_IBMR2", /* IBM RS-6000 (we ensured that aix is defined above */ -#ifndef aix -#define aix /* allow BOOTSTRAPCFLAGS="-D_IBMR2" */ -#endif -#endif /* _IBMR2 */ -#ifdef aix - "-Daix", /* AIX instead of AOS */ -#ifndef ibm -#define ibm /* allow BOOTSTRAPCFLAGS="-Daix" */ -#endif -#endif /* aix */ -#ifdef ibm - "-Dibm", /* IBM PS/2 and RT under both AOS and AIX */ -#endif -#ifdef luna - "-Dluna", /* OMRON luna 68K and 88K */ -#ifdef luna1 - "-Dluna1", -#endif -#ifdef luna88k /* need not on UniOS-Mach Vers. 1.13 */ - "-traditional", /* for some older version */ -#endif /* instead of "-DXCOMM=\\#" */ -#ifdef uniosb - "-Duniosb", -#endif -#ifdef uniosu - "-Duniosu", -#endif -#endif /* luna */ -#ifdef _CRAY /* Cray */ - "-Ucray", -#endif -#ifdef Mips - "-DMips", /* Define and use Mips for Mips Co. OS/mach. */ -# if defined(SYSTYPE_BSD) || defined(BSD) || defined(BSD43) - "-DBSD43", /* Mips RISCOS supports two environments */ -# else - "-DSYSV", /* System V environment is the default */ -# endif -#endif /* Mips */ -#ifdef MOTOROLA - "-DMOTOROLA", /* Motorola Delta Systems */ -# ifdef SYSV - "-DSYSV", -# endif -# ifdef SVR4 - "-DSVR4", -# endif -#endif /* MOTOROLA */ -#ifdef i386 -# ifdef SVR4 - "-Di386", - "-DSVR4", -# endif -# ifdef SYSV - "-Di386", - "-DSYSV", -# ifdef ISC - "-DISC", -# ifdef ISC40 - "-DISC40", /* ISC 4.0 */ -# else -# ifdef ISC202 - "-DISC202", /* ISC 2.0.2 */ -# else -# ifdef ISC30 - "-DISC30", /* ISC 3.0 */ -# else - "-DISC22", /* ISC 2.2.1 */ -# endif -# endif -# endif -# endif -# ifdef SCO - "-DSCO", -# ifdef SCO324 - "-DSCO324", -# endif -# endif -# endif -# ifdef ESIX - "-Di386", - "-DESIX", -# endif -# ifdef ATT - "-Di386", - "-DATT", -# endif -# ifdef DELL - "-Di386", - "-DDELL", -# endif -#endif -#ifdef SYSV386 /* System V/386 folks, obsolete */ - "-Di386", -# ifdef SVR4 - "-DSVR4", -# endif -# ifdef ISC - "-DISC", -# ifdef ISC40 - "-DISC40", /* ISC 4.0 */ -# else -# ifdef ISC202 - "-DISC202", /* ISC 2.0.2 */ -# else -# ifdef ISC30 - "-DISC30", /* ISC 3.0 */ -# else - "-DISC22", /* ISC 2.2.1 */ -# endif -# endif -# endif -# endif -# ifdef SCO - "-DSCO", -# ifdef SCO324 - "-DSCO324", -# endif -# endif -# ifdef ESIX - "-DESIX", -# endif -# ifdef ATT - "-DATT", -# endif -# ifdef DELL - "-DDELL", -# endif -#endif -#ifdef __osf__ - "-D__osf__", -# ifdef __mips__ - "-D__mips__", -# endif -# ifdef __alpha - "-D__alpha", -# endif -# ifdef __i386__ - "-D__i386__", -# endif -# ifdef __GNUC__ - "-traditional" -# endif -#endif -#ifdef Oki - "-DOki", -#endif -#ifdef sun -#ifdef SVR4 - "-DSVR4", -#endif -#endif -#ifdef WIN32 - "-DWIN32", - "-nologo", - "-batch", - "-D__STDC__", -#endif -#ifdef NCR - "-DNCR", /* NCR */ -#endif -#ifdef linux - "-traditional", - "-Dlinux", -#endif -#ifdef __uxp__ - "-D__uxp__", -#endif -#ifdef __sxg__ - "-D__sxg__", -#endif -#ifdef nec_ews_svr2 - "-Dnec_ews_svr2", -#endif -#ifdef AMOEBA - "-DAMOEBA", -# ifdef CROSS_COMPILE - "-DCROSS_COMPILE", -# ifdef CROSS_i80386 - "-Di80386", -# endif -# ifdef CROSS_sparc - "-Dsparc", -# endif -# ifdef CROSS_mc68000 - "-Dmc68000", -# endif -# else -# ifdef i80386 - "-Di80386", -# endif -# ifdef sparc - "-Dsparc", -# endif -# ifdef mc68000 - "-Dmc68000", -# endif -# endif -#endif -#ifdef __minix_vmd - "-Dminix", -#endif - -#if defined(__EMX__) - "-traditional", - "-Demxos2", -#endif - -}; -#else /* else MAKEDEPEND */ -/* - * Step 6: predefs - * If your compiler and/or preprocessor define any specific symbols, add - * them to the the following table. The definition of struct symtab is - * in util/makedepend/def.h. - */ -struct symtab predefs[] = { -#ifdef apollo - {"apollo", "1"}, -#endif -#ifdef ibm032 - {"ibm032", "1"}, -#endif -#ifdef ibm - {"ibm", "1"}, -#endif -#ifdef aix - {"aix", "1"}, -#endif -#ifdef sun - {"sun", "1"}, -#endif -#ifdef sun2 - {"sun2", "1"}, -#endif -#ifdef sun3 - {"sun3", "1"}, -#endif -#ifdef sun4 - {"sun4", "1"}, -#endif -#ifdef sparc - {"sparc", "1"}, -#endif -#ifdef __sparc__ - {"__sparc__", "1"}, -#endif -#ifdef hpux - {"hpux", "1"}, -#endif -#ifdef __hpux - {"__hpux", "1"}, -#endif -#ifdef __hp9000s800 - {"__hp9000s800", "1"}, -#endif -#ifdef __hp9000s700 - {"__hp9000s700", "1"}, -#endif -#ifdef vax - {"vax", "1"}, -#endif -#ifdef VMS - {"VMS", "1"}, -#endif -#ifdef cray - {"cray", "1"}, -#endif -#ifdef CRAY - {"CRAY", "1"}, -#endif -#ifdef _CRAY - {"_CRAY", "1"}, -#endif -#ifdef att - {"att", "1"}, -#endif -#ifdef mips - {"mips", "1"}, -#endif -#ifdef __mips__ - {"__mips__", "1"}, -#endif -#ifdef ultrix - {"ultrix", "1"}, -#endif -#ifdef stellar - {"stellar", "1"}, -#endif -#ifdef mc68000 - {"mc68000", "1"}, -#endif -#ifdef mc68020 - {"mc68020", "1"}, -#endif -#ifdef __GNUC__ - {(char *)"__GNUC__", (char *)"1"}, -#endif -#if __STDC__ - {(char *)"__STDC__", (char *)"1"}, -#endif -#ifdef __HIGHC__ - {"__HIGHC__", "1"}, -#endif -#ifdef CMU - {"CMU", "1"}, -#endif -#ifdef luna - {"luna", "1"}, -#ifdef luna1 - {"luna1", "1"}, -#endif -#ifdef luna2 - {"luna2", "1"}, -#endif -#ifdef luna88k - {"luna88k", "1"}, -#endif -#ifdef uniosb - {"uniosb", "1"}, -#endif -#ifdef uniosu - {"uniosu", "1"}, -#endif -#endif -#ifdef ieeep754 - {"ieeep754", "1"}, -#endif -#ifdef is68k - {"is68k", "1"}, -#endif -#ifdef m68k - {"m68k", "1"}, -#endif -#ifdef m88k - {"m88k", "1"}, -#endif -#ifdef __m88k__ - {"__m88k__", "1"}, -#endif -#ifdef bsd43 - {"bsd43", "1"}, -#endif -#ifdef hcx - {"hcx", "1"}, -#endif -#ifdef sony - {"sony", "1"}, -#ifdef SYSTYPE_SYSV - {"SYSTYPE_SYSV", "1"}, -#endif -#ifdef _SYSTYPE_SYSV - {"_SYSTYPE_SYSV", "1"}, -#endif -#endif -#ifdef __OSF__ - {"__OSF__", "1"}, -#endif -#ifdef __osf__ - {"__osf__", "1"}, -#endif -#ifdef __alpha - {"__alpha", "1"}, -#endif -#ifdef __DECC - {"__DECC", "1"}, -#endif -#ifdef __decc - {"__decc", "1"}, -#endif -#ifdef __uxp__ - {"__uxp__", "1"}, -#endif -#ifdef __sxg__ - {"__sxg__", "1"}, -#endif -#ifdef _SEQUENT_ - {"_SEQUENT_", "1"}, - {"__STDC__", "1"}, -#endif -#ifdef __bsdi__ - {"__bsdi__", "1"}, -#endif -#ifdef nec_ews_svr2 - {"nec_ews_svr2", "1"}, -#endif -#ifdef nec_ews_svr4 - {"nec_ews_svr4", "1"}, -#endif -#ifdef _nec_ews_svr4 - {"_nec_ews_svr4", "1"}, -#endif -#ifdef _nec_up - {"_nec_up", "1"}, -#endif -#ifdef SX - {"SX", "1"}, -#endif -#ifdef nec - {"nec", "1"}, -#endif -#ifdef _nec_ft - {"_nec_ft", "1"}, -#endif -#ifdef PC_UX - {(char *)"PC_UX", (char *)"1"}, -#endif -#ifdef __EMX__ - {"__EMX__", "1"}, -#endif - /* add any additional symbols before this line */ - {NULL, NULL} -}; - -#endif /* MAKEDEPEND */ -#endif /* CCIMAKE */ diff --git a/core/tools/dependency_tool/include.cpp b/core/tools/dependency_tool/include.cpp deleted file mode 100644 index 08ade608..00000000 --- a/core/tools/dependency_tool/include.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* $XConsortium: include.c,v 1.17 94/12/05 19:33:08 gildea Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "def.h" - -#include - -#ifdef _MSC_VER - #undef strcasecmp - #undef strncasecmp - #define strcasecmp strcmpi - #define strncasecmp strnicmp -#endif - -extern inclist inc_list[MAXFILES], *inclistp; -extern char *includedirs[ ]; -extern char *excludedirs[ ]; -extern char *notdotdot[ ]; -extern bool show_where_not; -extern bool warn_multiple; - -// forward. -void remove_dotdot(char *path); -int isdot(register char *p); -int isdotdot(register char *p); -int issymbolic(register char *dir, register char *component); -void included_by(register inclist *ip, register inclist *newfile); - -inclist *inc_path(register char *file, register char *include, bool dot, - bool &failure_okay) -{ - static char path[ BUFSIZ ]; - register char **pp, *p; - register inclist *ip; - struct stat st; - bool found = false; - -//fprintf(stderr, "file=%s include=%s\n", file, include); - const size_t inclen = strlen(include); - if (inclen >= 4) { - register char *cpp_point = include + inclen - 4; - if (!strcasecmp(".cpp", cpp_point)) { - // this is a CPP file include, which we skip. -//fprintf(stderr, "!found match at point: %s\n", cpp_point); -//hold failure_okay = true; -//hold return NULL; - } - } - -////////fprintf(stderr, "incpath entry\n"); - - /* - * Check all previously found include files for a path that - * has already been expanded. - */ - for (ip = inc_list; ip->i_file; ip++) - if ((strcmp(ip->i_incstring, include) == 0) && !ip->i_included_sym) { - found = true; - break; - } - - /* - * If the path was surrounded by "" or is an absolute path, - * then check the exact path provided. - */ - if (!found && (dot || *include == '/' || *include == '\\')) { - if (stat(include, &st) == 0) { - ip = newinclude(include, include); - found = true; - } - else if (show_where_not) - warning1("\tnot in %s\n", include); - } - - /* - * See if this include file is in the directory of the - * file being compiled. - */ - if (!found) { - for (p=file+strlen(file); p>file; p--) - if (*p == '/' || *p == '\\') - break; - if (p == file) - strcpy(path, include); - else { - strncpy(path, file, (p-file) + 1); - path[ (p-file) + 1 ] = '\0'; - strcpy(path + (p-file) + 1, include); - } - remove_dotdot(path); - if (stat(path, &st) == 0) { - ip = newinclude(path, include); - found = true; - } - else if (show_where_not) - warning1("\tnot in %s\n", path); - } - - /* - * Check the include directories specified. (standard include dir - * should be at the end.) - */ - if (!found) - for (pp = includedirs; *pp; pp++) { - sprintf(path, "%s/%s", *pp, include); - remove_dotdot(path); - if (stat(path, &st) == 0) { - register char **pp2; - bool exclude_it = false; - for (pp2 = excludedirs; *pp2; pp2++) { -////////fprintf(stderr, "comparing %s with %s\n", *pp, *pp2); - if (!strncmp(*pp, *pp2, strlen(*pp2))) { - // this file is supposed to be excluded. - exclude_it = true; - break; - } - } -////////if (exclude_it) fprintf(stderr, "excluding path %s\n", path); - if (exclude_it) { - failure_okay = true; - found = false; - } else { - ip = newinclude(path, include); - found = true; - } - break; - } - else if (show_where_not) - warning1("\tnot in %s\n", path); - } - - if (!found) - ip = NULL; - return(ip); -} - -/* - * Occasionally, pathnames are created that look like .../x/../y - * Any of the 'x/..' sequences within the name can be eliminated. - * (but only if 'x' is not a symbolic link!!) - */ -void remove_dotdot(char *path) -{ - register char *end, *from, *to, **cp; - char *components[ MAXFILES ], newpath[ BUFSIZ ]; - bool component_copied; - - /* - * slice path up into components. - */ - to = newpath; - if (*path == '/' || *path == '\\') - *to++ = '/'; - *to = '\0'; - cp = components; - for (from=end=path; *end; end++) - if (*end == '/' || *end == '\\') { - while (*end == '/' || *end == '\\') - *end++ = '\0'; - if (*from) - *cp++ = from; - from = end; - } - *cp++ = from; - *cp = NULL; - - /* - * Recursively remove all 'x/..' component pairs. - */ - cp = components; - while(*cp) { - if (!isdot(*cp) && !isdotdot(*cp) && isdotdot(*(cp+1)) - && !issymbolic(newpath, *cp)) - { - char **fp = cp + 2; - char **tp = cp; - - do - *tp++ = *fp; /* move all the pointers down */ - while (*fp++); - if (cp != components) - cp--; /* go back and check for nested ".." */ - } else { - cp++; - } - } - /* - * Concatenate the remaining path elements. - */ - cp = components; - component_copied = false; - while(*cp) { - if (component_copied) - *to++ = '/'; - component_copied = true; - for (from = *cp; *from; ) - *to++ = *from++; - *to = '\0'; - cp++; - } - *to++ = '\0'; - - /* - * copy the reconstituted path back to our pointer. - */ - strcpy(path, newpath); -} - -int isdot(register char *p) -{ - if(p && *p++ == '.' && *p++ == '\0') - return(true); - return(false); -} - -int isdotdot(register char *p) -{ - if(p && *p++ == '.' && *p++ == '.' && *p++ == '\0') - return(true); - return(false); -} - -int issymbolic(register char *dir, register char *component) -{ -#ifdef S_IFLNK - struct stat st; - char buf[ BUFSIZ ], **pp; - - sprintf(buf, "%s%s%s", dir, *dir ? "/" : "", component); - for (pp=notdotdot; *pp; pp++) - if (strcmp(*pp, buf) == 0) - return (true); - if (lstat(buf, &st) == 0 - && (st.st_mode & S_IFMT) == S_IFLNK) { - *pp++ = copy(buf); - if (pp >= ¬dotdot[ MAXDIRS ]) - fatalerr("out of .. dirs, increase MAXDIRS\n"); - return(true); - } -#endif - return(false); -} - -/* - * Add an include file to the list of those included by 'file'. - */ -inclist *newinclude(register char *newfile, register char *incstring) -{ - register inclist *ip; - - /* - * First, put this file on the global list of include files. - */ - ip = inclistp++; - if (inclistp == inc_list + MAXFILES - 1) - fatalerr("out of space: increase MAXFILES\n"); - ip->i_file = copy(newfile); - ip->i_included_sym = false; - if (incstring == NULL) - ip->i_incstring = ip->i_file; - else - ip->i_incstring = copy(incstring); - - return(ip); -} - -void included_by(register inclist *ip, register inclist *newfile) -{ - register int i; - - if (ip == NULL) - return; - /* - * Put this include file (newfile) on the list of files included - * by 'file'. If 'file' is NULL, then it is not an include - * file itself (i.e. was probably mentioned on the command line). - * If it is already on the list, don't stick it on again. - */ - if (ip->i_list == NULL) - ip->i_list = (inclist **) - malloc(sizeof(inclist *) * ++ip->i_listlen); - else { - for (i=0; ii_listlen; i++) - if (ip->i_list[ i ] == newfile) { - i = int(strlen(newfile->i_file)); - if (!ip->i_included_sym && - !(i > 2 && - newfile->i_file[i-1] == 'c' && - newfile->i_file[i-2] == '.')) - { - /* only complain if ip has */ - /* no #include SYMBOL lines */ - /* and is not a .c file */ - if (warn_multiple) - { - warning("%s includes %s more than once!\n", - ip->i_file, newfile->i_file); - warning1("Already have\n"); - for (i=0; ii_listlen; i++) - warning1("\t%s\n", ip->i_list[i]->i_file); - } - } - return; - } - ip->i_list = (inclist **) realloc(ip->i_list, - sizeof(inclist *) * ++ip->i_listlen); - } - ip->i_list[ ip->i_listlen-1 ] = newfile; -} - -void inc_clean() -{ - register inclist *ip; - - for (ip = inc_list; ip < inclistp; ip++) { - ip->i_marked = false; - } -} - diff --git a/core/tools/dependency_tool/makedep.cpp b/core/tools/dependency_tool/makedep.cpp deleted file mode 100644 index 83df3bf1..00000000 --- a/core/tools/dependency_tool/makedep.cpp +++ /dev/null @@ -1,785 +0,0 @@ -/* $XConsortium: main.c,v 1.84 94/11/30 16:10:44 kaleb Exp $ */ -/* $XFree86: xc/config/makedepend/main.c,v 3.3 1995/01/28 15:41:03 dawes Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "def.h" - -#ifdef _MSC_VER -#include -#else -#include -#endif -#include -#include - -#ifdef hpux -#define sigvec sigvector -#endif /* hpux */ - -#ifdef X_POSIX_C_SOURCE -#define _POSIX_C_SOURCE X_POSIX_C_SOURCE -#include -#undef _POSIX_C_SOURCE -#else -#if defined(X_NOT_POSIX) || defined(_POSIX_SOURCE) -#include -#else -#define _POSIX_SOURCE -#include -#undef _POSIX_SOURCE -#endif -#endif - -#if NeedVarargsPrototypes -#include -#endif - -#ifdef MINIX -#define USE_CHMOD 1 -#endif - -#ifdef DEBUG -int _debugmask; -#endif - -char *ProgramName; - -const char *directives[] = { - "if", - "ifdef", - "ifndef", - "else", - "endif", - "define", - "undef", - "include", - "line", - "pragma", - "error", - "ident", - "sccs", - "elif", - "eject", - "import", - NULL -}; - -#define MAKEDEPEND -#include "imakemdep.h" /* from config sources */ -#undef MAKEDEPEND - -struct inclist inc_list[ MAXFILES ], *inclistp = inc_list, maininclist; - -char *filelist[ MAXFILES ]; -char *includedirs[ MAXDIRS + 1 ]; -char *excludedirs[ MAXDIRS + 1 ]; -char *notdotdot[ MAXDIRS ]; -char *objprefix = (char *)""; -char *objsuffix = (char *)".obj"; /* OBJSUFFIX; */ -char *startat = (char *)"# DO NOT DELETE"; -int width = 78; -bool append = false; -bool printed = false; -bool verbose = false; -bool show_where_not = false; -bool warn_multiple = false; /* Warn on multiple includes of same file */ - -static -#ifdef SIGNALRETURNSINT -int -#else -void -#endif -c_catch(int sig) -{ - fflush (stdout); - fatalerr ((char *)"got signal %d\n", sig); -} - -#if defined(USG) || (defined(i386) && defined(SYSV)) || defined(WIN32) || defined(__EMX__) || defined(__OS2__) -#define USGISH -#endif - -#ifndef USGISH -#ifndef _POSIX_SOURCE -#define sigaction sigvec -#define sa_handler sv_handler -#define sa_mask sv_mask -#define sa_flags sv_flags -#endif -struct sigaction sig_act; -#endif /* USGISH */ - -/* fatty boombalatty, and wrong idea here. - -// adds any subdirectories under dirname into the list of -// include directories, so we can get a whole hierarchies set of -// include files. -void add_subdirs(char *dirname, char ** &incp) -{ - directory dir(dirname); - string_array subdirs = dir.directories(); - for (int i = 0; i < subdirs.length(); i++) { - istring curr = istring(dirname) + "/" + subdirs[i]; - // add the current subdirectory. - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = strdup(curr.s()); -printf((istring("added ") + curr + "\n").s()); - add_subdirs(curr.s(), incp); - } -} -*/ - -int main(int argc, char **argv) -{ - register char **fp = filelist; - register char **incp = includedirs; - register char **excp = excludedirs; - register char *p; - register struct inclist *ip; - char *makefile = NULL; - struct filepointer *filecontent; - struct symtab *psymp = predefs; - char *endmarker = NULL; - char *defincdir = NULL; - - ProgramName = argv[0]; - - while (psymp->s_name) - { - define2(psymp->s_name, psymp->s_value, &maininclist); - psymp++; - } - if (argc == 2 && argv[1][0] == '@') { - struct stat ast; - int afd; - char *args; - char **nargv; - int nargc; - char quotechar = '\0'; - - nargc = 1; - if ((afd = open(argv[1]+1, O_RDONLY)) < 0) - fatalerr("cannot open \"%s\"\n", argv[1]+1); - fstat(afd, &ast); - args = (char *)malloc(ast.st_size + 2); - if ((ast.st_size = read(afd, args, ast.st_size)) < 0) - fatalerr("failed to read %s\n", argv[1]+1); - args[ast.st_size] = '\n'; - args[ast.st_size + 1] = '\0'; - close(afd); - for (p = args; *p; p++) { - if (quotechar) { - if (quotechar == '\\' || - (*p == quotechar && p[-1] != '\\')) - quotechar = '\0'; - continue; - } - switch (*p) { - case '\\': - case '"': - case '\'': - quotechar = *p; - break; - case ' ': - case '\n': - *p = '\0'; - if (p > args && p[-1]) - nargc++; - break; - } - } - if (p[-1]) - nargc++; - nargv = (char **)malloc(nargc * sizeof(char *)); - nargv[0] = argv[0]; - argc = 1; - for (p = args; argc < nargc; p += strlen(p) + 1) - if (*p) nargv[argc++] = p; - argv = nargv; - } - for(argc--, argv++; argc; argc--, argv++) { - /* if looking for endmarker then check before parsing */ - if (endmarker && strcmp (endmarker, *argv) == 0) { - endmarker = NULL; - continue; - } - if (**argv != '-') { - /* treat +thing as an option for C++ */ - if (endmarker && **argv == '+') - continue; - *fp++ = argv[0]; - continue; - } - switch(argv[0][1]) { - case '-': - endmarker = &argv[0][2]; - if (endmarker[0] == '\0') endmarker = (char *)"--"; - break; - case 'D': - if (argv[0][2] == '\0') { - argv++; - argc--; - } - for (p=argv[0] + 2; *p ; p++) - if (*p == '=') { - *p = ' '; - break; - } - define(argv[0] + 2, &maininclist); - break; - case 'i': - { - char* delim; - char* envinclude; - char* prevdir; - if (endmarker) break; - if (argv[0][2] == '\0') { - argv++; - argc--; - envinclude = getenv(argv[0]); - } else envinclude = getenv(argv[0]+2); - if (!envinclude) break; - prevdir = envinclude; - delim = (char*)strchr(envinclude, ';'); - while(delim) - { - if (incp >= includedirs + MAXDIRS) fatalerr("Too many Include directories.\n"); - *delim = '\0'; - delim++; - *incp++ = prevdir; - prevdir = delim; - delim = (char*)strchr(delim, ';'); - } - } - break; - case 'X': -//fprintf(stderr, "adding Xclude %s\n", argv[0]+2); - // add a directory to the exclusions list. - if (excp >= excludedirs + MAXDIRS) - fatalerr("Too many -X flags.\n"); - *excp++ = argv[0]+2; - if (**(excp-1) == '\0') { - // fix the prior entry, but don't incremement yet; we'll do that - // on the include list instead. - *(excp-1) = *(argv + 1); - } - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags via -X.\n"); - *incp++ = argv[0]+2; - if (**(incp-1) == '\0') { - *(incp-1) = *(++argv); - argc--; - } - break; - case 'I': - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = argv[0]+2; - if (**(incp-1) == '\0') { - *(incp-1) = *(++argv); - argc--; - } -/// add_subdirs(*(incp-1), incp); - break; - case 'Y': - defincdir = argv[0]+2; - break; - /* do not use if endmarker processing */ - case 'a': - if (endmarker) break; - append = true; - break; - case 'w': - if (endmarker) break; - if (argv[0][2] == '\0') { - argv++; - argc--; - width = atoi(argv[0]); - } else - width = atoi(argv[0]+2); - break; - case 'o': - if (endmarker) break; - if (argv[0][2] == '\0') { - argv++; - argc--; - objsuffix = argv[0]; - } else - objsuffix = argv[0]+2; - break; - case 'p': - if (endmarker) break; - if (argv[0][2] == '\0') { - argv++; - argc--; - objprefix = argv[0]; - } else - objprefix = argv[0]+2; - break; - case 'v': - if (endmarker) break; - verbose = true; -#ifdef DEBUG - if (argv[0][2]) - _debugmask = atoi(argv[0]+2); -#endif - break; - case 's': - if (endmarker) break; - startat = argv[0]+2; - if (*startat == '\0') { - startat = *(++argv); - argc--; - } - if (*startat != '#') - fatalerr("-s flag's value should start %s\n", - "with '#'."); - break; - case 'f': - if (endmarker) break; - makefile = argv[0]+2; - if (*makefile == '\0') { - makefile = *(++argv); - argc--; - } - break; - case 'm': - warn_multiple = true; - break; - - /* Ignore -O, -g so we can just pass ${CFLAGS} to - makedepend - */ - case 'O': - case 'g': - break; - default: - if (endmarker) break; - /* fatalerr("unknown opt = %s\n", argv[0]); */ - warning("ignoring option %s\n", argv[0]); - } - } - - if (!defincdir) { -#ifdef PREINCDIR - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = PREINCDIR; -#endif -#ifdef INCLUDEDIR - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = INCLUDEDIR; -#endif -#ifdef POSTINCDIR - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = POSTINCDIR; -#endif - } else if (*defincdir) { - if (incp >= includedirs + MAXDIRS) - fatalerr("Too many -I flags.\n"); - *incp++ = defincdir; - } - - redirect(startat, makefile); - - /* - * c_catch signals. - */ -#ifdef USGISH -/* should really reset SIGINT to SIG_IGN if it was. */ -#ifdef SIGHUP - signal (SIGHUP, c_catch); -#endif - signal (SIGINT, c_catch); -#ifdef SIGQUIT - signal (SIGQUIT, c_catch); -#endif - signal (SIGILL, c_catch); -#ifdef SIGBUS - signal (SIGBUS, c_catch); -#endif - signal (SIGSEGV, c_catch); -#ifdef SIGSYS - signal (SIGSYS, c_catch); -#endif -#else - sig_act.sa_handler = c_catch; -#ifdef _POSIX_SOURCE - sigemptyset(&sig_act.sa_mask); - sigaddset(&sig_act.sa_mask, SIGINT); - sigaddset(&sig_act.sa_mask, SIGQUIT); -#ifdef SIGBUS - sigaddset(&sig_act.sa_mask, SIGBUS); -#endif - sigaddset(&sig_act.sa_mask, SIGILL); - sigaddset(&sig_act.sa_mask, SIGSEGV); - sigaddset(&sig_act.sa_mask, SIGHUP); - sigaddset(&sig_act.sa_mask, SIGPIPE); -#ifdef SIGSYS - sigaddset(&sig_act.sa_mask, SIGSYS); -#endif -#else - sig_act.sa_mask = ((1<<(SIGINT -1)) - |(1<<(SIGQUIT-1)) -#ifdef SIGBUS - |(1<<(SIGBUS-1)) -#endif - |(1<<(SIGILL-1)) - |(1<<(SIGSEGV-1)) - |(1<<(SIGHUP-1)) - |(1<<(SIGPIPE-1)) -#ifdef SIGSYS - |(1<<(SIGSYS-1)) -#endif - ); -#endif /* _POSIX_SOURCE */ - sig_act.sa_flags = 0; - sigaction(SIGHUP, &sig_act, (struct sigaction *)0); - sigaction(SIGINT, &sig_act, (struct sigaction *)0); - sigaction(SIGQUIT, &sig_act, (struct sigaction *)0); - sigaction(SIGILL, &sig_act, (struct sigaction *)0); -#ifdef SIGBUS - sigaction(SIGBUS, &sig_act, (struct sigaction *)0); -#endif - sigaction(SIGSEGV, &sig_act, (struct sigaction *)0); -#ifdef SIGSYS - sigaction(SIGSYS, &sig_act, (struct sigaction *)0); -#endif -#endif /* USGISH */ - - /* - * now peruse through the list of files. - */ - for(fp=filelist; *fp; fp++) { - filecontent = getfile(*fp); - ip = newinclude(*fp, (char *)NULL); - - find_includes(filecontent, ip, ip, 0, false); - freefile(filecontent); - recursive_pr_include(ip, ip->i_file, base_name(*fp)); - inc_clean(); - } - if (printed) - printf("\n"); - - return 0; -} - -struct filepointer *getfile(char *file) -{ - register int fd; - struct filepointer *content; - struct stat st; - - content = (struct filepointer *)malloc(sizeof(struct filepointer)); - content->f_name = strdup(file); - if ((fd = open(file, O_RDONLY)) < 0) { - warning("cannot open \"%s\"\n", file); - content->f_p = content->f_base = content->f_end = (char *)malloc(1); - *content->f_p = '\0'; - return(content); - } - fstat(fd, &st); - content->f_base = (char *)malloc(st.st_size+1); - if (content->f_base == NULL) - fatalerr("cannot allocate mem\n"); - if ((st.st_size = read(fd, content->f_base, st.st_size)) < 0) - fatalerr("failed to read %s\n", file); - close(fd); - content->f_len = st.st_size+1; - content->f_p = content->f_base; - content->f_end = content->f_base + st.st_size; - *content->f_end = '\0'; - content->f_line = 0; - return(content); -} - -void freefile(struct filepointer *fp) -{ - free(fp->f_name); - free(fp->f_base); - free(fp); -} - -char *copy(register char *str) -{ - register char *p = (char *)malloc(strlen(str) + 1); - - strcpy(p, str); - return(p); -} - -int match(register const char *str, register const char **list) -{ - register int i; - - for (i=0; *list; i++, list++) - if (strcmp(str, *list) == 0) - return i; - return -1; -} - -/* - * Get the next line. We only return lines beginning with '#' since that - * is all this program is ever interested in. - */ -char *getline(register struct filepointer *filep) -{ - register char *p, /* walking pointer */ - *eof, /* end of file pointer */ - *bol; /* beginning of line pointer */ - register int lineno; /* line number */ - - eof = filep->f_end; -//// if (p >= eof) return NULL; - lineno = filep->f_line; - bol = filep->f_p; - - // p is always pointing at the "beginning of a line" when we start this loop. - // this means that we must start considering the stuff on that line as - // being a useful thing to look at. - for (p = filep->f_p; p < eof; p++) { -if (bol > p) fatalerr("somehow bol got ahead of p."); - if (*p == '/' && *(p+1) == '*') { - /* consume C-style comments */ - *p++ = ' '; *p++ = ' '; // skip the two we've already seen. - while (p < eof) { - if (*p == '*' && *(p+1) == '/') { - *p++ = ' '; *p = ' '; - // skip one of these last two, let the loop skip the next. - break; - } else if (*p == '\n') lineno++; - p++; // skip the current character. - } - continue; - } else if (*p == '/' && *(p+1) == '/') { - /* consume C++-style comments */ - *p++ = ' '; *p++ = ' '; // skip the comment characters. - while (p < eof && (*p != '\n') ) *p++ = ' '; - // we scan until we get to the end of line. -///no count, since we'll see it again. lineno++; - p--; // skip back to just before \n. - continue; // let the loop skip past the eol that we saw. - } else if (*p == '\\') { - // handle an escape character. - if (*(p+1) == '\n') { - // we modify the stream so we consider the line correctly. - *p = ' '; - *(p+1) = ' '; - lineno++; - } - } else if (*p == '\n') { - // we've finally reached the end of the line. - lineno++; - *p = '\0'; // set the end of line to be a end of string now. - if (bol < p) { - // it's not at the same position as the end of line, so it's worth - // considering. - while ( (bol < p) && ((*bol == ' ') || (*bol == '\t')) ) bol++; -////fprintf(stderr, "%s: %s\n", filep->f_name, bol); -////fflush(stderr); - if (*bol == '#') { - register char *cp; - /* punt lines with just # (yacc generated) */ - for (cp = bol+1; *cp && (*cp == ' ' || *cp == '\t'); cp++) {} - if (*cp) { p++; goto done; } - } - } - // this is a failure now. we reset the beginning of line. - bol = p+1; - } - } - if (*bol != '#') bol = NULL; -done: - filep->f_p = p; - filep->f_line = lineno; - return bol; -} - -/* - * Strip the file name down to what we want to see in the Makefile. - * It will have objprefix and objsuffix around it. - */ -char *base_name(register char *file) -{ - register char *p; - - file = copy(file); - for(p=file+strlen(file); p>file && *p != '.'; p--) ; - - if (*p == '.') - *p = '\0'; - return(file); -} - -#if defined(USG) && !defined(CRAY) && !defined(SVR4) && !defined(__EMX__) -int rename(char *from, char *to) -{ - (void) unlink (to); - if (link (from, to) == 0) { - unlink (from); - return 0; - } else { - return -1; - } -} -#endif /* USGISH */ - -void redirect(char *line, char *makefile) -{ - struct stat st; - FILE *fdin, *fdout; - char backup[ BUFSIZ ], - buf[ BUFSIZ ]; - bool found = false; - int len; - - /* - * if makefile is "-" then let it pour onto stdout. - */ - if (makefile && *makefile == '-' && *(makefile+1) == '\0') - return; - - /* - * use a default makefile is not specified. - */ - if (!makefile) { - if (stat("Makefile", &st) == 0) - makefile = (char *)"Makefile"; - else if (stat("makefile", &st) == 0) - makefile = (char *)"makefile"; - else - fatalerr("[mM]akefile is not present\n"); - } - else - stat(makefile, &st); - if ((fdin = fopen(makefile, "r")) == NULL) - fatalerr("cannot open \"%s\"\n", makefile); - sprintf(backup, "%s.bak", makefile); - unlink(backup); -#if defined(WIN32) || defined(__EMX__) || defined(__OS2__) - fclose(fdin); -#endif - if (rename(makefile, backup) < 0) - fatalerr("cannot rename %s to %s\n", makefile, backup); -#if defined(WIN32) || defined(__EMX__) || defined(__OS2__) - if ((fdin = fopen(backup, "r")) == NULL) - fatalerr("cannot open \"%s\"\n", backup); -#endif - if ((fdout = freopen(makefile, "w", stdout)) == NULL) - fatalerr("cannot open \"%s\"\n", backup); - len = int(strlen(line)); - while (!found && fgets(buf, BUFSIZ, fdin)) { - if (*buf == '#' && strncmp(line, buf, len) == 0) - found = true; - fputs(buf, fdout); - } - if (!found) { - if (verbose) - warning("Adding new delimiting line \"%s\" and dependencies...\n", - line); - puts(line); /* same as fputs(fdout); but with newline */ - } else if (append) { - while (fgets(buf, BUFSIZ, fdin)) { - fputs(buf, fdout); - } - } - fflush(fdout); -#if defined(USGISH) || defined(_SEQUENT_) || defined(USE_CHMOD) - chmod(makefile, st.st_mode); -#else - fchmod(fileno(fdout), st.st_mode); -#endif /* USGISH */ -} - -#if NeedVarargsPrototypes -void fatalerr(const char *msg, ...) -#else -/*VARARGS*/ -void fatalerr(char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) -#endif -{ -#if NeedVarargsPrototypes - va_list args; -#endif - fprintf(stderr, "%s: error: ", ProgramName); -#if NeedVarargsPrototypes - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); -#else - fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); -#endif - exit (1); -} - -#if NeedVarargsPrototypes -void warning(const char *msg, ...) -#else -/*VARARGS0*/ -void warning(const char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) -#endif -{ -#if NeedVarargsPrototypes - va_list args; -#endif - fprintf(stderr, "%s: warning: ", ProgramName); -#if NeedVarargsPrototypes - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); -#else - fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); -#endif -} - -#if NeedVarargsPrototypes -void warning1(const char *msg, ...) -#else -/*VARARGS0*/ -void warning1(const char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) -#endif -{ -#if NeedVarargsPrototypes - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); -#else - fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); -#endif -} - diff --git a/core/tools/dependency_tool/makedepend.man b/core/tools/dependency_tool/makedepend.man deleted file mode 100644 index 9c3cdccd..00000000 --- a/core/tools/dependency_tool/makedepend.man +++ /dev/null @@ -1,368 +0,0 @@ -.\" $XConsortium: mkdepend.man,v 1.15 94/04/17 20:10:37 gildea Exp $ -.\" Copyright (c) 1993, 1994 X Consortium -.\" -.\" Permission is hereby granted, free of charge, to any person obtaining a -.\" copy of this software and associated documentation files (the "Software"), -.\" to deal in the Software without restriction, including without limitation -.\" the rights to use, copy, modify, merge, publish, distribute, sublicense, -.\" and/or sell copies of the Software, and to permit persons to whom the -.\" Software furnished to do so, subject to the following conditions: -.\" -.\" The above copyright notice and this permission notice shall be included in -.\" all copies or substantial portions of the Software. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -.\" THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -.\" WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF -.\" OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -.\" SOFTWARE. -.\" -.\" Except as contained in this notice, the name of the X Consortium shall not -.\" be used in advertising or otherwise to promote the sale, use or other -.\" dealing in this Software without prior written authorization from the -.\" X Consortium. -.TH MAKEDEPEND 1 "Release 6" "X Version 11" -.UC 4 -.SH NAME -makedepend \- create dependencies in makefiles -.SH SYNOPSIS -.B makedepend -[ -.B \-Dname=def -] [ -.B \-Dname -] [ -.B \-Iincludedir -] [ -.B \-Yincludedir -] [ -.B \-a -] [ -.B \-fmakefile -] [ -.B \-oobjsuffix -] [ -.B \-pobjprefix -] [ -.B \-sstring -] [ -.B \-wwidth -] [ -.B \-v -] [ -.B \-m -] [ -\-\^\- -.B otheroptions -\-\^\- -] -sourcefile .\|.\|. -.br -.SH DESCRIPTION -.B Makedepend -reads each -.I sourcefile -in sequence and parses it like a C-preprocessor, -processing all -.I #include, -.I #define, -.I #undef, -.I #ifdef, -.I #ifndef, -.I #endif, -.I #if -and -.I #else -directives so that it can correctly tell which -.I #include, -directives would be used in a compilation. -Any -.I #include, -directives can reference files having other -.I #include -directives, and parsing will occur in these files as well. -.PP -Every file that a -.I sourcefile -includes, -directly or indirectly, -is what -.B makedepend -calls a "dependency". -These dependencies are then written to a -.I makefile -in such a way that -.B make(1) -will know which object files must be recompiled when a dependency has changed. -.PP -By default, -.B makedepend -places its output in the file named -.I makefile -if it exists, otherwise -.I Makefile. -An alternate makefile may be specified with the -.B \-f -option. -It first searches the makefile for -the line -.sp - # DO NOT DELETE THIS LINE \-\^\- make depend depends on it. -.sp -or one provided with the -.B \-s -option, -as a delimiter for the dependency output. -If it finds it, it will delete everything -following this to the end of the makefile -and put the output after this line. -If it doesn't find it, the program -will append the string to the end of the makefile -and place the output following that. -For each -.I sourcefile -appearing on the command line, -.B makedepend -puts lines in the makefile of the form -.sp - sourcefile.o:\0dfile .\|.\|. -.sp -Where "sourcefile.o" is the name from the command -line with its suffix replaced with ".o", -and "dfile" is a dependency discovered in a -.I #include -directive while parsing -.I sourcefile -or one of the files it included. -.SH EXAMPLE -Normally, -.B makedepend -will be used in a makefile target so that typing "make depend" will -bring the dependencies up to date for the makefile. -For example, -.nf - SRCS\0=\0file1.c\0file2.c\0.\|.\|. - CFLAGS\0=\0\-O\0\-DHACK\0\-I\^.\^.\^/foobar\0\-xyz - depend: - makedepend\0\-\^\-\0$(CFLAGS)\0\-\^\-\0$(SRCS) -.fi -.SH OPTIONS -.B Makedepend -will ignore any option that it does not understand so that you may use -the same arguments that you would for -.B cc(1). -.TP 5 -.B \-Dname=def or \-Dname -Define. -This places a definition for -.I name -in -.B makedepend's -symbol table. -Without -.I =def -the symbol becomes defined as "1". -.TP 5 -.B \-Iincludedir -Include directory. -This option tells -.B makedepend -to prepend -.I includedir -to its list of directories to search when it encounters -a -.I #include -directive. -By default, -.B makedepend -only searches the standard include directories (usually /usr/include -and possibly a compiler-dependent directory). -.TP 5 -.B \-Yincludedir -Replace all of the standard include directories with the single specified -include directory; you can omit the -.I includedir -to simply prevent searching the standard include directories. -.TP 5 -.B \-a -Append the dependencies to the end of the file instead of replacing them. -.TP 5 -.B \-fmakefile -Filename. -This allows you to specify an alternate makefile in which -.B makedepend -can place its output. -.TP 5 -.B \-oobjsuffix -Object file suffix. -Some systems may have object files whose suffix is something other -than ".o". -This option allows you to specify another suffix, such as -".b" with -.I -o.b -or ":obj" -with -.I -o:obj -and so forth. -.TP 5 -.B \-pobjprefix -Object file prefix. -The prefix is prepended to the name of the object file. This is -usually used to designate a different directory for the object file. -The default is the empty string. -.TP 5 -.B \-sstring -Starting string delimiter. -This option permits you to specify -a different string for -.B makedepend -to look for in the makefile. -.TP 5 -.B \-wwidth -Line width. -Normally, -.B makedepend -will ensure that every output line that it writes will be no wider than -78 characters for the sake of readability. -This option enables you to change this width. -.TP 5 -.B \-v -Verbose operation. -This option causes -.B makedepend -to emit the list of files included by each input file on standard output. -.TP 5 -.B \-m -Warn about multiple inclusion. -This option causes -.B makedepend -to produce a warning if any input file includes another file more than -once. In previous versions of -.B makedepend -this was the default behavior; the default has been changed to better -match the behavior of the C compiler, which does not consider multiple -inclusion to be an error. This option is provided for backward -compatibility, and to aid in debugging problems related to multiple -inclusion. -.TP 5 -.B "\-\^\- options \-\^\-" -If -.B makedepend -encounters a double hyphen (\-\^\-) in the argument list, -then any unrecognized argument following it -will be silently ignored; a second double hyphen terminates this -special treatment. -In this way, -.B makedepend -can be made to safely ignore esoteric compiler arguments that might -normally be found in a CFLAGS -.B make -macro (see the -.B EXAMPLE -section above). -All options that -.B makedepend -recognizes and appear between the pair of double hyphens -are processed normally. -.SH ALGORITHM -The approach used in this program enables it to run an order of magnitude -faster than any other "dependency generator" I have ever seen. -Central to this performance are two assumptions: -that all files compiled by a single -makefile will be compiled with roughly the same -.I -I -and -.I -D -options; -and that most files in a single directory will include largely the -same files. -.PP -Given these assumptions, -.B makedepend -expects to be called once for each makefile, with -all source files that are maintained by the -makefile appearing on the command line. -It parses each source and include -file exactly once, maintaining an internal symbol table -for each. -Thus, the first file on the command line will take an amount of time -proportional to the amount of time that a normal C preprocessor takes. -But on subsequent files, if it encounter's an include file -that it has already parsed, it does not parse it again. -.PP -For example, -imagine you are compiling two files, -.I file1.c -and -.I file2.c, -they each include the header file -.I header.h, -and the file -.I header.h -in turn includes the files -.I def1.h -and -.I def2.h. -When you run the command -.sp - makedepend\0file1.c\0file2.c -.sp -.B makedepend -will parse -.I file1.c -and consequently, -.I header.h -and then -.I def1.h -and -.I def2.h. -It then decides that the dependencies for this file are -.sp - file1.o:\0header.h\0def1.h\0def2.h -.sp -But when the program parses -.I file2.c -and discovers that it, too, includes -.I header.h, -it does not parse the file, -but simply adds -.I header.h, -.I def1.h -and -.I def2.h -to the list of dependencies for -.I file2.o. -.SH "SEE ALSO" -cc(1), make(1) -.SH BUGS -.B makedepend -parses, but does not currently evaluate, the SVR4 -#predicate(token-list) preprocessor expression; -such expressions are simply assumed to be true. -This may cause the wrong -.I #include -directives to be evaluated. -.PP -Imagine you are parsing two files, -say -.I file1.c -and -.I file2.c, -each includes the file -.I def.h. -The list of files that -.I def.h -includes might truly be different when -.I def.h -is included by -.I file1.c -than when it is included by -.I file2.c. -But once -.B makedepend -arrives at a list of dependencies for a file, -it is cast in concrete. -.SH AUTHOR -Todd Brunhoff, Tektronix, Inc. and MIT Project Athena diff --git a/core/tools/dependency_tool/makedepend.txt b/core/tools/dependency_tool/makedepend.txt deleted file mode 100644 index e8a90f76..00000000 --- a/core/tools/dependency_tool/makedepend.txt +++ /dev/null @@ -1,264 +0,0 @@ - - - -User Commands MAKEDEPEND(1) - - - -NNAAMMEE - makedepend - create dependencies in makefiles - -SSYYNNOOPPSSIISS - mmaakkeeddeeppeenndd [ --DDnnaammee==ddeeff ] [ --DDnnaammee ] [ --IIiinncclluuddeeddiirr ] [ - --YYiinncclluuddeeddiirr ] [ --aa ] [ --ffmmaakkeeffiillee ] [ --oooobbjjssuuffffiixx ] [ --ppoobb-- - jjpprreeffiixx ] [ --ssssttrriinngg ] [ --wwwwiiddtthh ] [ --vv ] [ --mm ] [ -- ootthh-- - eerrooppttiioonnss -- ] sourcefile ... - -DDEESSCCRRIIPPTTIIOONN - MMaakkeeddeeppeenndd reads each _s_o_u_r_c_e_f_i_l_e in sequence and parses it - like a C-preprocessor, processing all _#_i_n_c_l_u_d_e_, _#_d_e_f_i_n_e_, - _#_u_n_d_e_f_, _#_i_f_d_e_f_, _#_i_f_n_d_e_f_, _#_e_n_d_i_f_, _#_i_f and _#_e_l_s_e directives so - that it can correctly tell which _#_i_n_c_l_u_d_e_, directives would - be used in a compilation. Any _#_i_n_c_l_u_d_e_, directives can ref- - erence files having other _#_i_n_c_l_u_d_e directives, and parsing - will occur in these files as well. - - Every file that a _s_o_u_r_c_e_f_i_l_e includes, directly or indi- - rectly, is what mmaakkeeddeeppeenndd calls a "dependency". These - dependencies are then written to a _m_a_k_e_f_i_l_e in such a way - that mmaakkee((11)) will know which object files must be recompiled - when a dependency has changed. - - By default, mmaakkeeddeeppeenndd places its output in the file named - _m_a_k_e_f_i_l_e if it exists, otherwise _M_a_k_e_f_i_l_e_. An alternate - makefile may be specified with the --ff option. It first - searches the makefile for the line - - # DO NOT DELETE THIS LINE -- make depend depends on it. - - or one provided with the --ss option, as a delimiter for the - dependency output. If it finds it, it will delete every- - thing following this to the end of the makefile and put the - output after this line. If it doesn't find it, the program - will append the string to the end of the makefile and place - the output following that. For each _s_o_u_r_c_e_f_i_l_e appearing on - the command line, mmaakkeeddeeppeenndd puts lines in the makefile of - the form - - sourcefile.o: dfile ... - - Where "sourcefile.o" is the name from the command line with - its suffix replaced with ".o", and "dfile" is a dependency - discovered in a _#_i_n_c_l_u_d_e directive while parsing _s_o_u_r_c_e_f_i_l_e - or one of the files it included. - -EEXXAAMMPPLLEE - Normally, mmaakkeeddeeppeenndd will be used in a makefile target so - that typing "make depend" will bring the dependencies up to - date for the makefile. For example, - SRCS = file1.c file2.c ... - - - -X Version 11 Last change: Release 6 1 - - - - - - -User Commands MAKEDEPEND(1) - - - - CFLAGS = -O -DHACK -I../foobar -xyz - depend: - makedepend -- $(CFLAGS) -- $(SRCS) - -OOPPTTIIOONNSS - MMaakkeeddeeppeenndd will ignore any option that it does not under- - stand so that you may use the same arguments that you would - for cccc((11)).. - - --DDnnaammee==ddeeff oorr --DDnnaammee - Define. This places a definition for _n_a_m_e in mmaakkeeddee-- - ppeenndd''ss symbol table. Without _=_d_e_f the symbol becomes - defined as "1". - - --IIiinncclluuddeeddiirr - Include directory. This option tells mmaakkeeddeeppeenndd to - prepend _i_n_c_l_u_d_e_d_i_r to its list of directories to search - when it encounters a _#_i_n_c_l_u_d_e directive. By default, - mmaakkeeddeeppeenndd only searches the standard include directo- - ries (usually /usr/include and possibly a compiler- - dependent directory). - - --YYiinncclluuddeeddiirr - Replace all of the standard include directories with - the single specified include directory; you can omit - the _i_n_c_l_u_d_e_d_i_r to simply prevent searching the standard - include directories. - - --aa Append the dependencies to the end of the file instead - of replacing them. - - --ffmmaakkeeffiillee - Filename. This allows you to specify an alternate - makefile in which mmaakkeeddeeppeenndd can place its output. - - --oooobbjjssuuffffiixx - Object file suffix. Some systems may have object files - whose suffix is something other than ".o". This option - allows you to specify another suffix, such as ".b" with - _-_o_._b or ":obj" with _-_o_:_o_b_j and so forth. - - --ppoobbjjpprreeffiixx - Object file prefix. The prefix is prepended to the - name of the object file. This is usually used to desig- - nate a different directory for the object file. The - default is the empty string. - - --ssssttrriinngg - Starting string delimiter. This option permits you to - specify a different string for mmaakkeeddeeppeenndd to look for - in the makefile. - - - - -X Version 11 Last change: Release 6 2 - - - - - - -User Commands MAKEDEPEND(1) - - - - --wwwwiiddtthh - Line width. Normally, mmaakkeeddeeppeenndd will ensure that - every output line that it writes will be no wider than - 78 characters for the sake of readability. This option - enables you to change this width. - - --vv Verbose operation. This option causes mmaakkeeddeeppeenndd to - emit the list of files included by each input file on - standard output. - - --mm Warn about multiple inclusion. This option causes - mmaakkeeddeeppeenndd to produce a warning if any input file - includes another file more than once. In previous ver- - sions of mmaakkeeddeeppeenndd this was the default behavior; the - default has been changed to better match the behavior - of the C compiler, which does not consider multiple - inclusion to be an error. This option is provided for - backward compatibility, and to aid in debugging prob- - lems related to multiple inclusion. - - ---- ooppttiioonnss ---- - If mmaakkeeddeeppeenndd encounters a double hyphen (--) in the - argument list, then any unrecognized argument following - it will be silently ignored; a second double hyphen - terminates this special treatment. In this way, - mmaakkeeddeeppeenndd can be made to safely ignore esoteric com- - piler arguments that might normally be found in a - CFLAGS mmaakkee macro (see the EEXXAAMMPPLLEE section above). All - options that mmaakkeeddeeppeenndd recognizes and appear between - the pair of double hyphens are processed normally. - -AALLGGOORRIITTHHMM - The approach used in this program enables it to run an order - of magnitude faster than any other "dependency generator" I - have ever seen. Central to this performance are two assump- - tions: that all files compiled by a single makefile will be - compiled with roughly the same _-_I and _-_D options; and that - most files in a single directory will include largely the - same files. - - Given these assumptions, mmaakkeeddeeppeenndd expects to be called - once for each makefile, with all source files that are main- - tained by the makefile appearing on the command line. It - parses each source and include file exactly once, maintain- - ing an internal symbol table for each. Thus, the first file - on the command line will take an amount of time proportional - to the amount of time that a normal C preprocessor takes. - But on subsequent files, if it encounter's an include file - that it has already parsed, it does not parse it again. - - For example, imagine you are compiling two files, _f_i_l_e_1_._c - and _f_i_l_e_2_._c_, they each include the header file _h_e_a_d_e_r_._h_, and - - - -X Version 11 Last change: Release 6 3 - - - - - - -User Commands MAKEDEPEND(1) - - - - the file _h_e_a_d_e_r_._h in turn includes the files _d_e_f_1_._h and - _d_e_f_2_._h_. When you run the command - - makedepend file1.c file2.c - - mmaakkeeddeeppeenndd will parse _f_i_l_e_1_._c and consequently, _h_e_a_d_e_r_._h and - then _d_e_f_1_._h and _d_e_f_2_._h_. It then decides that the dependen- - cies for this file are - - file1.o: header.h def1.h def2.h - - But when the program parses _f_i_l_e_2_._c and discovers that it, - too, includes _h_e_a_d_e_r_._h_, it does not parse the file, but sim- - ply adds _h_e_a_d_e_r_._h_, _d_e_f_1_._h and _d_e_f_2_._h to the list of depen- - dencies for _f_i_l_e_2_._o_. - -SSEEEE AALLSSOO - cc(1), make(1) - -BBUUGGSS - mmaakkeeddeeppeenndd parses, but does not currently evaluate, the SVR4 - #predicate(token-list) preprocessor expression; such expres- - sions are simply assumed to be true. This may cause the - wrong _#_i_n_c_l_u_d_e directives to be evaluated. - - Imagine you are parsing two files, say _f_i_l_e_1_._c and _f_i_l_e_2_._c_, - each includes the file _d_e_f_._h_. The list of files that _d_e_f_._h - includes might truly be different when _d_e_f_._h is included by - _f_i_l_e_1_._c than when it is included by _f_i_l_e_2_._c_. But once - mmaakkeeddeeppeenndd arrives at a list of dependencies for a file, it - is cast in concrete. - -AAUUTTHHOORR - Todd Brunhoff, Tektronix, Inc. and MIT Project Athena - - - - - - - - - - - - - - - - - - - - - -X Version 11 Last change: Release 6 4 - - - diff --git a/core/tools/dependency_tool/makefile b/core/tools/dependency_tool/makefile deleted file mode 100644 index 14967053..00000000 --- a/core/tools/dependency_tool/makefile +++ /dev/null @@ -1,17 +0,0 @@ -CONSOLE_MODE = true - -include cpp/variables.def - -STRICT_WARNINGS = - -PROJECT = dependency_tool -TYPE = application -SOURCE = cppsetup.cpp ifparser.cpp include.cpp parse.cpp pr.cpp -#DEFINITIONS += __BUILD_STATIC_APPLICATION__ -ifeq "$(OP_SYSTEM)" "WIN32" - SOURCE += makedep_version.rc -endif -TARGETS = makedep.exe - -include cpp/rules.def - diff --git a/core/tools/dependency_tool/parse.cpp b/core/tools/dependency_tool/parse.cpp deleted file mode 100644 index 6bb46237..00000000 --- a/core/tools/dependency_tool/parse.cpp +++ /dev/null @@ -1,554 +0,0 @@ -/* $XConsortium: parse.c,v 1.30 94/04/17 20:10:38 gildea Exp $ */ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "def.h" - -#include - -extern const char *directives[]; -extern inclist maininclist; - -int find_includes(struct filepointer *filep, inclist *file, inclist *file_red, int recursion, bool failOK) -{ - register char *line; - register int type; - bool recfailOK; - - while ((line = getline(filep))) { - switch(type = deftype(line, filep, file_red, file, true)) { - case IF: - doif: - type = find_includes(filep, file, - file_red, recursion+1, failOK); - while ((type == ELIF) || (type == ELIFFALSE) || - (type == ELIFGUESSFALSE)) - type = gobble(filep, file, file_red); - if (type == ELSE) - gobble(filep, file, file_red); - break; - case IFFALSE: - case IFGUESSFALSE: - doiffalse: - if (type == IFGUESSFALSE || type == ELIFGUESSFALSE) - recfailOK = true; - else - recfailOK = failOK; - type = gobble(filep, file, file_red); - if (type == ELSE) - find_includes(filep, file, - file_red, recursion+1, recfailOK); - else - if (type == ELIF) - goto doif; - else - if ((type == ELIFFALSE) || (type == ELIFGUESSFALSE)) - goto doiffalse; - break; - case IFDEF: - case IFNDEF: - if ((type == IFDEF && isdefined(line, file_red, NULL)) - || (type == IFNDEF && !isdefined(line, file_red, NULL))) { - debug(1,(type == IFNDEF ? - "line %d: %s !def'd in %s via %s%s\n" : "", - filep->f_line, line, - file->i_file, file_red->i_file, ": doit")); - type = find_includes(filep, file, - file_red, recursion+1, failOK); - while (type == ELIF || type == ELIFFALSE || type == ELIFGUESSFALSE) - type = gobble(filep, file, file_red); - if (type == ELSE) - gobble(filep, file, file_red); - } - else { - debug(1,(type == IFDEF ? - "line %d: %s !def'd in %s via %s%s\n" : "", - filep->f_line, line, - file->i_file, file_red->i_file, ": gobble")); - type = gobble(filep, file, file_red); - if (type == ELSE) - find_includes(filep, file, - file_red, recursion+1, failOK); - else if (type == ELIF) - goto doif; - else if (type == ELIFFALSE || type == ELIFGUESSFALSE) - goto doiffalse; - } - break; - case ELSE: - case ELIFFALSE: - case ELIFGUESSFALSE: - case ELIF: - if (!recursion) - gobble(filep, file, file_red); - case ENDIF: - if (recursion) - return(type); - case DEFINE: - define(line, file); - break; - case UNDEF: - if (!*line) { - warning("%s, line %d: incomplete undef == \"%s\"\n", - file_red->i_file, filep->f_line, line); - break; - } - undefine(line, file_red); - break; - case INCLUDE: - add_include(filep, file, file_red, line, false, failOK); - break; - case INCLUDEDOT: - add_include(filep, file, file_red, line, true, failOK); - break; - case ERROR: - warning("%s: %d: %s\n", file_red->i_file, - filep->f_line, line); - break; - - case PRAGMA: - case IDENT: - case SCCS: - case EJECT: - case IMPORT: - break; - case -1: - warning("%s", file_red->i_file); - if (file_red != file) - warning1(" (reading %s)", file->i_file); - warning1(", line %d: unknown directive == \"%s\"\n", - filep->f_line, line); - break; - case -2: - warning("%s", file_red->i_file); - if (file_red != file) - warning1(" (reading %s)", file->i_file); - warning1(", line %d: incomplete include == \"%s\"\n", - filep->f_line, line); - break; - } - } - return(-1); -} - -int gobble(register struct filepointer *filep, inclist *file, - inclist *file_red) -{ - register char *line; - register int type; - - while ((line = getline(filep))) { - switch(type = deftype(line, filep, file_red, file, false)) { - case IF: - case IFFALSE: - case IFGUESSFALSE: - case IFDEF: - case IFNDEF: - type = gobble(filep, file, file_red); - while ((type == ELIF) || (type == ELIFFALSE) || - (type == ELIFGUESSFALSE)) - type = gobble(filep, file, file_red); - if (type == ELSE) - (void)gobble(filep, file, file_red); - break; - case ELSE: - case ENDIF: - debug(0,("%s, line %d: #%s\n", - file->i_file, filep->f_line, - directives[type])); - return(type); - case DEFINE: - case UNDEF: - case INCLUDE: - case INCLUDEDOT: - case PRAGMA: - case ERROR: - case IDENT: - case SCCS: - case EJECT: - case IMPORT: - break; - case ELIF: - case ELIFFALSE: - case ELIFGUESSFALSE: - return(type); - case -1: - warning("%s, line %d: unknown directive == \"%s\"\n", - file_red->i_file, filep->f_line, line); - break; - } - } - return(-1); -} - -/* - * Decide what type of # directive this line is. - */ -int deftype(register char *line, register struct filepointer *filep, - register inclist *file_red, register inclist *file, - int parse_it) -{ - register char *p; - char *directive, savechar; - register int ret; - - /* - * Parse the directive... - */ - directive=line+1; - while (*directive == ' ' || *directive == '\t') - directive++; - - p = directive; - while (*p >= 'a' && *p <= 'z') - p++; - savechar = *p; - *p = '\0'; - ret = match(directive, directives); - *p = savechar; - - /* If we don't recognize this compiler directive or we happen to just - * be gobbling up text while waiting for an #endif or #elif or #else - * in the case of an #elif we must check the zero_value and return an - * ELIF or an ELIFFALSE. - */ - - if (ret == ELIF && !parse_it) - { - while (*p == ' ' || *p == '\t') - p++; - /* - * parse an expression. - */ - debug(0,("%s, line %d: #elif %s ", - file->i_file, filep->f_line, p)); - ret = zero_value(p, filep, file_red); - if (ret != IF) - { - debug(0,("false...\n")); - if (ret == IFFALSE) - return(ELIFFALSE); - else - return(ELIFGUESSFALSE); - } - else - { - debug(0,("true...\n")); - return(ELIF); - } - } - - if (ret < 0 || ! parse_it) - return(ret); - - /* - * now decide how to parse the directive, and do it. - */ - while (*p == ' ' || *p == '\t') - p++; - switch (ret) { - case IF: - /* - * parse an expression. - */ - ret = zero_value(p, filep, file_red); - debug(0,("%s, line %d: %s #if %s\n", - file->i_file, filep->f_line, ret?"false":"true", p)); - break; - case IFDEF: - case IFNDEF: - debug(0,("%s, line %d: #%s %s\n", - file->i_file, filep->f_line, directives[ret], p)); - case UNDEF: - /* - * separate the name of a single symbol. - */ - while (isalnum(*p) || *p == '_') - *line++ = *p++; - *line = '\0'; - break; - case INCLUDE: - debug(2,("%s, line %d: #include %s\n", - file->i_file, filep->f_line, p)); - - /* Support ANSI macro substitution */ - { - struct symtab *sym = isdefined(p, file_red, NULL); - while (sym) { - p = sym->s_value; - debug(3,("%s : #includes SYMBOL %s = %s\n", - file->i_incstring, - sym -> s_name, - sym -> s_value)); - /* mark file as having included a 'soft include' */ - file->i_included_sym = true; - sym = isdefined(p, file_red, NULL); - } - } - - /* - * Separate the name of the include file. - */ - while (*p && *p != '"' && *p != '<') - p++; - if (! *p) - return(-2); - if (*p++ == '"') { - ret = INCLUDEDOT; - while (*p && *p != '"') - *line++ = *p++; - } else - while (*p && *p != '>') - *line++ = *p++; - *line = '\0'; - break; - case DEFINE: - /* - * copy the definition back to the beginning of the line. - */ - strcpy (line, p); - break; - case ELSE: - case ENDIF: - case ELIF: - case PRAGMA: - case ERROR: - case IDENT: - case SCCS: - case EJECT: - case IMPORT: - debug(0,("%s, line %d: #%s\n", - file->i_file, filep->f_line, directives[ret])); - /* - * nothing to do. - */ - break; - } - return(ret); -} - -symtab *isdefined(register char *symbol, inclist *file, - inclist **srcfile) -{ - register struct symtab *val; - - if ((val = slookup(symbol, &maininclist))) { - debug(1,("%s defined on command line\n", symbol)); - if (srcfile != NULL) *srcfile = &maininclist; - return(val); - } - if ((val = fdefined(symbol, file, srcfile))) - return(val); - debug(1,("%s not defined in %s\n", symbol, file->i_file)); - return(NULL); -} - -struct symtab *fdefined(register char *symbol, inclist *file, inclist **srcfile) -{ - register inclist **ip; - register struct symtab *val; - register int i; - static int recurse_lvl = 0; - - if (file->i_defchecked) - return(NULL); - file->i_defchecked = true; - if ((val = slookup(symbol, file))) - debug(1,("%s defined in %s as %s\n", symbol, file->i_file, val->s_value)); - if (val == NULL && file->i_list) - { - for (ip = file->i_list, i=0; i < file->i_listlen; i++, ip++) - if ((val = fdefined(symbol, *ip, srcfile))) { - break; - } - } - else if (val != NULL && srcfile != NULL) *srcfile = file; - recurse_lvl--; - file->i_defchecked = false; - - return(val); -} - -/* - * Return type based on if the #if expression evaluates to 0 - */ -int zero_value(register char *exp, register struct filepointer *filep, - register inclist *file_red) -{ - if (cppsetup(exp, filep, file_red)) - return(IFFALSE); - else - return(IF); -} - -void define(char *def, inclist *file) -{ - char *val; - - /* Separate symbol name and its value */ - val = def; - while (isalnum(*val) || *val == '_') - val++; - if (*val) - *val++ = '\0'; - while (*val == ' ' || *val == '\t') - val++; - - if (!*val) - val = (char *)"1"; - define2(def, val, file); -} - -void define2(char *name, char *val, inclist *file) -{ - int first, last, below; - register struct symtab *sp = NULL, *dest; - - /* Make space if it's needed */ - if (file->i_defs == NULL) - { - file->i_defs = (struct symtab *) - malloc(sizeof (struct symtab) * SYMTABINC); - file->i_deflen = SYMTABINC; - file->i_ndefs = 0; - } - else if (file->i_ndefs == file->i_deflen) - file->i_defs = (struct symtab *) - realloc(file->i_defs, - sizeof(struct symtab)*(file->i_deflen+=SYMTABINC)); - - if (file->i_defs == NULL) - fatalerr("malloc()/realloc() failure in insert_defn()\n"); - - below = first = 0; - last = file->i_ndefs - 1; - while (last >= first) - { - /* Fast inline binary search */ - register char *s1; - register char *s2; - register int middle = (first + last) / 2; - - /* Fast inline strchr() */ - s1 = name; - s2 = file->i_defs[middle].s_name; - while (*s1++ == *s2++) - if (s2[-1] == '\0') break; - - /* If exact match, set sp and break */ - if (*--s1 == *--s2) - { - sp = file->i_defs + middle; - break; - } - - /* If name > i_defs[middle] ... */ - if (*s1 > *s2) - { - below = first; - first = middle + 1; - } - /* else ... */ - else - { - below = last = middle - 1; - } - } - - /* Search is done. If we found an exact match to the symbol name, - just replace its s_value */ - if (sp != NULL) - { - free(sp->s_value); - sp->s_value = copy(val); - return; - } - - sp = file->i_defs + file->i_ndefs++; - dest = file->i_defs + below + 1; - while (sp > dest) - { - *sp = sp[-1]; - sp--; - } - sp->s_name = copy(name); - sp->s_value = copy(val); -} - -struct symtab *slookup(register char *symbol, register inclist *file) -{ - register int first = 0; - register int last = file->i_ndefs - 1; - - if (file) while (last >= first) - { - /* Fast inline binary search */ - register char *s1; - register char *s2; - register int middle = (first + last) / 2; - - /* Fast inline strchr() */ - s1 = symbol; - s2 = file->i_defs[middle].s_name; - while (*s1++ == *s2++) - if (s2[-1] == '\0') break; - - /* If exact match, we're done */ - if (*--s1 == *--s2) - { - return file->i_defs + middle; - } - - /* If symbol > i_defs[middle] ... */ - if (*s1 > *s2) - { - first = middle + 1; - } - /* else ... */ - else - { - last = middle - 1; - } - } - return(NULL); -} - -void undefine(char *symbol, register inclist *file) -{ - register struct symtab *ptr; - inclist *srcfile; - while ((ptr = isdefined(symbol, file, &srcfile)) != NULL) - { - srcfile->i_ndefs--; - for (; ptr < srcfile->i_defs + srcfile->i_ndefs; ptr++) - *ptr = ptr[1]; - } -} diff --git a/core/tools/dependency_tool/pr.cpp b/core/tools/dependency_tool/pr.cpp deleted file mode 100644 index e1bdf7b3..00000000 --- a/core/tools/dependency_tool/pr.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - -Copyright (c) 1993, 1994 X Consortium - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of the X Consortium shall not be -used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization from the X Consortium. - -*/ - -#ifdef __WIN32__ - #pragma warning(disable : 4996) -#endif - -#include "def.h" - -#include - -extern struct inclist inc_list[ MAXFILES ], *inclistp; -extern char *objprefix; -extern char *objsuffix; -extern int width; -extern bool printed; -extern bool verbose; -extern bool show_where_not; - -void add_include(filepointer *filep, inclist *file, - inclist *file_red, char *include, bool dot, bool failOK) -{ - register struct inclist *newfile; - register struct filepointer *content; - - /* - * First decide what the pathname of this include file really is. - */ - newfile = inc_path(file->i_file, include, dot, failOK); - if (newfile == NULL) { - if (failOK) - return; - if (file != file_red) - warning("%s (reading %s, line %d): ", - file_red->i_file, file->i_file, filep->f_line); - else - warning("%s, line %d: ", file->i_file, filep->f_line); - warning1("cannot find include file \"%s\"\n", include); -fatalerr("cannot find include file \"%s\"\n", include); - show_where_not = true; - newfile = inc_path(file->i_file, include, dot, failOK); - show_where_not = false; - } - - if (newfile) { - included_by(file, newfile); - if (!newfile->i_searched) { - newfile->i_searched = true; - content = getfile(newfile->i_file); - find_includes(content, newfile, file_red, 0, failOK); - freefile(content); - } - } -} - -void recursive_pr_include(register struct inclist *head, register char *file, - register char *base) -{ - register int i; - - if (head->i_marked) - return; - head->i_marked = true; - if (head->i_file != file) { - bool rc_file = false; - if ((strlen(file) >= 3) && !strcmp(file + strlen(file) - 3, ".rc")) - rc_file = true; - pr(head, file, base, rc_file); - } - for (i=0; ii_listlen; i++) - recursive_pr_include(head->i_list[ i ], file, base); -} - -void pr(register struct inclist *ip, char *file, char *base, bool rc_file) -{ - static char *lastfile; - static int current_len; - register int len, i; - char buf[ BUFSIZ ]; - - printed = true; - len = int(strlen(ip->i_file)+1); - if (current_len + len > width || file != lastfile) { - lastfile = file; - char *temp_suff = objsuffix; - if (rc_file) temp_suff = (char *)".res"; - sprintf(buf, "\n%s%s%s: %s ", objprefix, base, temp_suff, ip->i_file); - len = current_len = int(strlen(buf)); - } - else { - strcpy(buf, ip->i_file); -//printf("ip->file=%s\n", ip->i_file); - char tmp[2] = { ' ', '\0' }; -//printf("tmp=%s\n", tmp); - strcat(buf, tmp); -//printf("buf=%s\n", buf); - current_len += len; - } - fwrite(buf, len, 1, stdout); - - // If verbose is set, then print out what this file includes. - if (! verbose || ip->i_list == NULL || ip->i_notified) - return; - ip->i_notified = true; - lastfile = NULL; - printf("\n# %s includes:", ip->i_file); - for (i=0; ii_listlen; i++) - printf("\n#\t%s", ip->i_list[ i ]->i_incstring); -} - diff --git a/core/tools/dependency_tool/readme.txt b/core/tools/dependency_tool/readme.txt deleted file mode 100644 index e9f660e2..00000000 --- a/core/tools/dependency_tool/readme.txt +++ /dev/null @@ -1,55 +0,0 @@ - -this port of makedepend is now called makedep, since it's a shorter name, -and therefore better...? or at least shorter. -the code has been ported to visual c++ 5.x-7.x as well as remaining compatible -with unix and linux. it has been made to comply with c++ prototype rules. -also, support for excluding directories from dependency checking has been -added (with a -X flag that takes the directory name). - -Chris Koeritz -fred@gruntose.com -(original 3/4/1999) -(updated 9/7/2000) -(updated 3/18/2004) - -============================================================================ - -makedepend ----------- - -This is a quick and rude port of the X11 R6 makedepend to OS/2 using icc. - -I have taken the code from FreeBSD 2.0.5, which I happened to have handy. - -I have added a feature I wanted: the switch -iENVIRONMENTVARIABLE will add -all semicolon delimited directories in ENVIRONMENTVARIABLE to the list of -include directories. - -One obvious use is: makedepend -i INCLUDE a.c - -If you do not want the system header files in you dependencies, you might use: - -set MYINCLUDE=\mytree\include;\mytree\subproj\include - -makedepend -i MYINCLUDE a.c - -but - -makedepend -I \mytree\include -I \mytree\subproj\include a.c - -which is the 'normal' way of doing things, is also possible. - -Sources -------- - -Todd Brunhoff wrote this program. Thanks. - -To build makedepend, I use icc and GNU make. nmake will barf over the makefile. - -And yes, I use long filenames. If you don't like that, edit the makefile. - -I have compiled these sources under NT with VC++ 4.2 without any hassles (different -makefile, though). - -Lars Immisch -lars@ibp.de diff --git a/core/tools/dependency_tool/version.ini b/core/tools/dependency_tool/version.ini deleted file mode 100644 index 8444aa59..00000000 --- a/core/tools/dependency_tool/version.ini +++ /dev/null @@ -1,5 +0,0 @@ -[version] -description=Makefile Dependency Generator -root=makedep -name=Dependency_Maker -extension=exe diff --git a/core/tools/makefile b/core/tools/makefile deleted file mode 100644 index f3781eeb..00000000 --- a/core/tools/makefile +++ /dev/null @@ -1,7 +0,0 @@ -include variables.def - -PROJECT = tools_hierarchy -BUILD_BEFORE = clam_tools dependency_tool simple_utilities solution_solvers - -include rules.def - diff --git a/core/tools/simple_utilities/create_guid.cpp b/core/tools/simple_utilities/create_guid.cpp deleted file mode 100644 index 104bde72..00000000 --- a/core/tools/simple_utilities/create_guid.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/*****************************************************************************\ -* * -* Name : create_guid * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* This program generates a globally unique identifier using the operating * -* system's support. The resulting id can be used to tag items that must be * -* uniquely named. * -* * -******************************************************************************* -* Copyright (c) 2006-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif - -using namespace application; -using namespace basis; -using namespace loggers; -using namespace mathematics; -using namespace structures; -using namespace textual; - -#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) - -// this is an example GUID in the DCE format: -// -// {12345678-1234-1234-1234-123456789012} -// -// each position can be a hexadecimal digit, ranging from 0 to F. -// the full size is measured as 32 nibbles or 16 bytes or 128 bits. - -class create_guid : public application_shell -{ -public: - create_guid() : application_shell() {} - DEFINE_CLASS_NAME("create_guid"); - int execute(); -}; - -int create_guid::execute() -{ -// FUNCDEF("execute"); - SETUP_CONSOLE_LOGGER; -#ifdef __UNIX__ - -// this is completely bogus for the time being. it just produces a random -// number rather than a guid. - #define add_random \ - faux_guid += astring(string_manipulation::hex_to_char \ - (randomizer().inclusive(0, 0xf)), 1) - - astring faux_guid("{"); - for (int i = 0; i < 8; i++) add_random; - faux_guid += "-"; - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 4; i++) add_random; - faux_guid += "-"; - } - for (int i = 0; i < 8; i++) add_random; - faux_guid += "}"; - BASE_LOG(faux_guid.lower()); -#elif defined (__WIN32__) - GUID guid; - CoCreateGuid(&guid); - const int BUFFER_SIZE = 1024; - LPOLESTR wide_buffer = new WCHAR[BUFFER_SIZE + 4]; - StringFromGUID2(guid, wide_buffer, BUFFER_SIZE); - const int BYTE_BUFFER_SIZE = BUFFER_SIZE * 2 + 4; - char buffer[BYTE_BUFFER_SIZE]; - WideCharToMultiByte(CP_UTF8, 0, wide_buffer, -1, buffer, BYTE_BUFFER_SIZE, - NULL, NULL); - astring guid_text = buffer; - delete [] wide_buffer; - BASE_LOG(guid_text); -#else - #error unknown operating system; no support for guids. -#endif - - return 0; -} - -HOOPLE_MAIN(create_guid, ) - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/simple_utilities/makefile b/core/tools/simple_utilities/makefile deleted file mode 100644 index ee683578..00000000 --- a/core/tools/simple_utilities/makefile +++ /dev/null @@ -1,27 +0,0 @@ -CONSOLE_MODE = t - -include cpp/variables.def - -PROJECT = simplistic_utils -TYPE = application -TARGETS = create_guid.exe playsound.exe short_path.exe sleep_ms.exe \ - zap_process.exe -ifeq "$(OMIT_VERSIONS)" "" - SOURCE += simple_utils_version.rc -endif -DEFINITIONS += __BUILD_STATIC_APPLICATION__ -UNDEFINITIONS += ENABLE_MEMORY_HOOK ENABLE_CALLSTACK_TRACKING -ifeq "$(OP_SYSTEM)" "WIN32" - # static C runtime support... -#hmmm: resurrect this as a particular build type, so makefiles don't need to know this... - COMPILER_FLAGS += -MT - LIBS_USED += netapi32.lib - ifeq "$(DEBUG)" "" - LIBS_USED += libcmt.lib - else - LIBS_USED += libcmtd.lib - endif -endif - -include cpp/rules.def - diff --git a/core/tools/simple_utilities/playsound.cpp b/core/tools/simple_utilities/playsound.cpp deleted file mode 100644 index c1ddf4b2..00000000 --- a/core/tools/simple_utilities/playsound.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/*****************************************************************************\ -* * -* Name : playsound * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* A program intended to be as simple as possible and to play only WAV * -* files on the win32 platform. It was decided that this was needed because * -* windows media player suddenly became political and started complaining * -* when other programs were registered to play different sound types. * -* All this does is play WAV files. That's it. * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include - -#ifdef __WIN32__ - #include -#endif - -using namespace basis; -using namespace loggers; -using namespace structures; - -///HOOPLE_STARTUP_CODE; -//hmmm: this needs to arise from obscurity too? - -int main(int argc, char *argv[]) -{ - console_logger out; - if (argc < 2) { - out.log(astring(argv[0]) + " usage:", ALWAYS_PRINT); - out.log(astring("This program takes one or more parameters which are interpreted"), ALWAYS_PRINT); - out.log(astring("as the names of sound files."), ALWAYS_PRINT); - return 12; - } - for (int i = 1; i < argc; i++) { -// out.log(astring(astring::SPRINTF, "soundfile %d: %s", i, argv[i])); -#ifdef __WIN32__ - if (!PlaySound(to_unicode_temp(argv[i]), NIL, SND_FILENAME)) - out.log(astring("failed to play ") + argv[i], ALWAYS_PRINT); -#else - out.log(astring("this program is a NO-OP, ignoring ") + argv[i], ALWAYS_PRINT); -#endif - } - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/simple_utilities/short_path.cpp b/core/tools/simple_utilities/short_path.cpp deleted file mode 100644 index b9582492..00000000 --- a/core/tools/simple_utilities/short_path.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/*****************************************************************************\ -* * -* Name : short_path * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* This program converts a pathname to its 8.3 name. Only for windows. * -* * -******************************************************************************* -* Copyright (c) 2007-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include - -#include -#include -#ifdef __WIN32__ - #include -#endif - -using namespace basis; -using namespace structures; - -///HOOPLE_STARTUP_CODE; - -int main(int argc, char *argv[]) -{ - astring shorty('\0', 2048); - if (argc < 2) { - printf("This program needs a path to convert to its short form.\n"); - return 23; - } -#ifdef __WIN32__ - GetShortPathNameA(argv[1], shorty.s(), 2045); -#else - strcpy(shorty.s(), argv[1]); -#endif - shorty.replace_all('\\', '/'); - printf("%s", shorty.s()); - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/simple_utilities/sleep_ms.cpp b/core/tools/simple_utilities/sleep_ms.cpp deleted file mode 100644 index 3b3d87ed..00000000 --- a/core/tools/simple_utilities/sleep_ms.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/*****************************************************************************\ -* * -* Name : sleep_ms * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Takes a number from the command line and sleeps for that many milli- * -* seconds. * -* * -******************************************************************************* -* Copyright (c) 2001-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include - -#include - -using namespace application; -using namespace basis; -using namespace timely; - -DEFINE_ARGC_AND_ARGV; -///DEFINE_INSTANCE_HANDLE; - -int main(int argc, char *argv[]) -{ - if (argc < 2) { - printf("%s usage:\nThe first parameter is taken as the number of " - "milliseconds to sleep.\n", argv[0]); - return 1; - } - - int snooze_ms; - sscanf(argv[1], "%d", &snooze_ms); - time_control::sleep_ms(snooze_ms); - return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/simple_utilities/version.ini b/core/tools/simple_utilities/version.ini deleted file mode 100644 index c1e1b1d8..00000000 --- a/core/tools/simple_utilities/version.ini +++ /dev/null @@ -1,6 +0,0 @@ -[version] -description = Simple Utility Collection -root = simple_utils -name = Simple Utilities -extension = exe - diff --git a/core/tools/simple_utilities/zap_process.cpp b/core/tools/simple_utilities/zap_process.cpp deleted file mode 100644 index 70473765..00000000 --- a/core/tools/simple_utilities/zap_process.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/*****************************************************************************\ -* * -* Name : zap_process * -* Author : Chris Koeritz * -* * -* Purpose: * -* * -* Whacks a process named on the command line, if possible. * -* * -******************************************************************************* -* Copyright (c) 2000-$now By Author. 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 2 of * -* the License or (at your option) any later version. This is online at: * -* http://www.fsf.org/copyleft/gpl.html * -* Please send any updates to: fred@gruntose.com * -\*****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//HOOPLE_STARTUP_CODE; -//hmmm: missing - -using namespace application; -using namespace basis; -using namespace filesystem; -using namespace loggers; -using namespace processes; -using namespace structures; - -int main(int argc, char *argv[]) -{ - console_logger out; - command_line cmds(argc, argv); - if ( (cmds.entries() < 1) - || (cmds.get(0).type() != command_parameter::VALUE) ) { - out.log(cmds.program_name().basename().raw() + " usage:\n" - "this takes a single parameter, which is the name of a program\n" - "to hunt down and eradicate. this will zap the program without\n" - "any warning or any chance for it to save its state.", ALWAYS_PRINT); - return 1; - } - astring program_name = cmds.get(0).text(); - program_name.to_lower(); - process_entry_array processes; - process_control proc_con; - if (!proc_con.query_processes(processes)) - non_continuable_error("procto", "main", "failed to query processes!"); - bool found = false; - bool success = true; - for (int i = 0; i < processes.length(); i++) { - filename path = processes[i].path(); -// out.log(astring("got process path: ") + path.raw(), ALWAYS_PRINT); - astring base = path.basename().raw().lower(); - if (base == program_name) { - found = true; -// out.log("would whack this entry:"); -// out.log(processes[i].text_form()); - bool ret = proc_con.zap_process(processes[i]._process_id); - if (ret) - out.log(a_sprintf("Killed process %d [", processes[i]._process_id) - + program_name + astring("]"), ALWAYS_PRINT); - else { - out.log(astring(astring::SPRINTF, "Failed to zap process %d [", - processes[i]._process_id) + program_name + astring("]"), ALWAYS_PRINT); - success = false; - } - } - } - if (!found) - out.log(astring("Could not find the program named ") + program_name, ALWAYS_PRINT); - if (!success) return 123; - else return 0; -} - -#ifdef __BUILD_STATIC_APPLICATION__ - // static dependencies found by buildor_gen_deps.sh: - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif // __BUILD_STATIC_APPLICATION__ - diff --git a/core/tools/solution_solvers/check_resource_ids.sh b/core/tools/solution_solvers/check_resource_ids.sh deleted file mode 100644 index d4b9dab9..00000000 --- a/core/tools/solution_solvers/check_resource_ids.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/bash - -# finds all the resource ids in resource headers (only those named the -# canonical resource.h name) and discovers any duplicates. the duplicates -# are shown with their symbolic names and file locations. - -TEMP_RESOURCE_HEADERS=/tmp/resrc_headers.txt - -sp='[ ]' # space and tab. - -# find all the resource headers so we can look at their contents. -find "$BUILD_TOP" -type f -iname "resource.h" | \ - grep -vi 3rdparty | \ - grep -vi admin_items | \ - grep -v app >"$TEMP_RESOURCE_HEADERS" -#hmmm: above ignores *anything* with app in the name. -# grep -v app_src >"$TEMP_RESOURCE_HEADERS" - -FULLDEFS=/tmp/full_definition_list.txt -# clean up prior versions. -rm -f "$FULLDEFS" - -# iterate through all the resource headers we found. -while read line; do -#echo "file=$line" - # find any lines that define a resource id. remove any that are part of - # visual studio's tracking system for next id to assign (_APS_NEXT crud). - chop_line="$(echo $line | sed -e 's/[\\\/]/+/g')" - grep "^$sp*#define$sp*[_A-Za-z0-9][_A-Za-z0-9]*$sp*[0-9][0-9]*$sp*$" <"$line" | \ - grep -v "_APS_NEXT" | \ - grep -v "_APS_3D" | \ - grep -v "$sp*\/\/.*" | \ - sed -e "s/^$sp*#define$sp*\([_A-Za-z0-9][_A-Za-z0-9]*\)$sp*\([0-9][0-9]*\)$sp*$/\1=\2#$chop_line/" >>"$FULLDEFS" -done <"$TEMP_RESOURCE_HEADERS" - -# our accumulated lists of names and ids (in order per list). -declare -a resource_names=() -declare -a resource_ids=() -declare -a resource_files=() - -# iterate through the definitions list and compile the set of known ids. -while read line; do - name=$(echo $line | sed -e 's/\([^=]*\)=[^#]*#.*/\1/') - id=$(echo $line | sed -e 's/[^=]*=\([^#]*\)#.*/\1/') - file=$(echo $line | sed -e 's/[^=]*=[^#]*#\(.*\)/\1/') - -#echo got name $name -#echo got id $id -#echo got file $file - next_index=${#resource_names[*]} -#echo next ind is $next_index - resource_names[${next_index}]=$name - resource_ids[${next_index}]=$id - resource_files[${next_index}]=$id - -done <"$FULLDEFS" - -echo done reading all definitions. - -JUST_IDS=/tmp/ids_list.txt -rm -f "$JUST_IDS" - -i=0 -while [[ i -le $next_index ]]; do - echo ${resource_ids[$i]} >>"$JUST_IDS" - ((i++)) -done - -echo done accumulating list of integer ids. - -id_size=$(wc "$JUST_IDS") - -JUST_IDS_TEMP=/tmp/ids_list_temp.txt - -sort "$JUST_IDS" | uniq >"$JUST_IDS_TEMP" -id_temp_size=$(wc "$JUST_IDS_TEMP") -if [ "$id_size" == "$id_temp_size" ]; then - echo "Your IDs are all unique! Ending analysis." - exit 0 -fi - -echo "Your ids are *NOT* all unique; the repeated ones are:" -sort "$JUST_IDS" | uniq -d >"$JUST_IDS_TEMP" - -while read line; do - id="$line" - echo "=== identifier $id ===" - grep "=$line#" "$FULLDEFS" | sed -e 's/\+/\//g' | sed -e 's/#/\ -/' -done <"$JUST_IDS_TEMP" - - diff --git a/core/tools/solution_solvers/clean_vcxproj.sh b/core/tools/solution_solvers/clean_vcxproj.sh deleted file mode 100644 index 8f74ba4a..00000000 --- a/core/tools/solution_solvers/clean_vcxproj.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# updates a vcxproj that had been converted from prior visual studio 2005 -# to the newer 2010. - -parms=($*) - -outdir="$HOME/fixed_proj" - -if [ ! -d "$outdir" ]; then mkdir -p $outdir; fi - -for i in ${parms[*]}; do - curr_parm="$i" - base=$(basename "$curr_parm") - echo fixing $base - - cat "$curr_parm" | - sed -e 's/v80<\/PlatformToolset>/v100<\/PlatformToolset>/g' | - sed -e 's/ide_files/build/g' | - sed -e 's/release_[de][lx][el]/release/g' | - sed -e 's/debug_[de][lx][el]/debug/g' | - sed -e 's///g' | - sed -e 's///g' | - sed -e 's///g' | - sed -e 's/v2.0<\/TargetFrameworkVersion>/v4.0<\/TargetFrameworkVersion>/g' | - sed -e 's/\.\.\\\.\.\\\.\.\\build/\.\.\\\.\.\\\.\.\\\.\.\\\.\.\\build/g' | - sed -e 's/\.\.\\\.\.\\lib_src/\.\.\\\.\.\\\.\.\\\.\.\\\.\.\\libraries/g' | - cat >"$outdir/$base" - -done - diff --git a/core/tools/solution_solvers/extract_projects.sh b/core/tools/solution_solvers/extract_projects.sh deleted file mode 100644 index c36525c0..00000000 --- a/core/tools/solution_solvers/extract_projects.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# this is a simple script that finds the project files listed in a solution file. - -solution_name="$1"; shift -if [ -z "$solution_name" ]; then - echo This script needs a solution or project file name. It will locate all the - echo projects listed in that file. - exit 3 -fi - -grep -i proj "$solution_name" | sed -n -e 's/.*"\([^"]*proj\)".*/\1/p' | sed -e 's/.*[\\\/]\([^\\\/]*\)/\1/' | tr A-Z a-z | sort | uniq - diff --git a/core/tools/solution_solvers/find_multilisted_projects_in_solutions.sh b/core/tools/solution_solvers/find_multilisted_projects_in_solutions.sh deleted file mode 100644 index 75ee4a31..00000000 --- a/core/tools/solution_solvers/find_multilisted_projects_in_solutions.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# this script checks all the known solution files to ensure that no project file is -# listed more than once in the whole set. - -export errors_seen=0 - -# where we'll send our generated files. -export OUT_DIR="$TMP" - -export CORE_PROJ="$OUT_DIR/core_projects.txt" -export SHARED_PROJ="$OUT_DIR/shared_projects.txt" -export MIDDLEWARE_PROJ="$OUT_DIR/middleware_projects.txt" - -#hmmm: fix references! -# extract all the project names from the solution files. -bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/libraries/solutions/lightlink_core.sln" >"$CORE_PROJ" -bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/libraries/solutions/lightlink_shared.sln" >"$SHARED_PROJ" -bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/products/Middleware/middleware.sln" >"$MIDDLEWARE_PROJ" - -export TEMP_COMPARE_FILE="$OUT_DIR/commonlines.txt" - -# outer loop is all but the most dependent project. -for i in "$CORE_PROJ" "$SHARED_PROJ"; do - # inner loop is all but the most depended on project. - for j in "$SHARED_PROJ" "$MIDDLEWARE_PROJ"; do - if [ "$i" == "$j" ]; then continue; fi -#echo comparing $i and $j - comm -1 -2 "$i" "$j" >"$TEMP_COMPARE_FILE" - if [ -s "$TEMP_COMPARE_FILE" ]; then - echo "ERROR: the two files $(basename $i) and $(basename $j) share common projects." - ((errors_seen++)) - fi - done -done - -if [ $errors_seen -gt 0 ]; then - echo "ERROR: There were errors detected during the checks!" - exit 3 -else - echo "No problems were seen in any checks." -fi - diff --git a/core/tools/solution_solvers/find_output_pathers.sh b/core/tools/solution_solvers/find_output_pathers.sh deleted file mode 100644 index 204fd327..00000000 --- a/core/tools/solution_solvers/find_output_pathers.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -# this script locates any project files that mention the output path setting (OutputPath). -# these are extra suspicious due to the problems caused for our solutions when files have the -# OutputPath specified rather than allowing the parent's settings to be inherited. - -# only works when repo dir is at the top of the full builds area. -# we need like a top dir of some sort. -find $REPOSITORY_DIR -iname "*proj" -exec grep -l OutputPath {} ';' >~/outputpath_mentioners.txt diff --git a/core/tools/solution_solvers/verify_project_file.sh b/core/tools/solution_solvers/verify_project_file.sh deleted file mode 100644 index 841bdf4d..00000000 --- a/core/tools/solution_solvers/verify_project_file.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# this script locates the solution that a project file belongs in and decides whether the -# project file contains any bad references to projects that are outside of its solution. - -proj_file="$1"; shift - -if [ -z "$proj_file" ]; then - echo This script needs one parameter that is a project file to be verified. - echo The file will be located in a solution file, and then checked for all project - echo references being in the same solution file. - exit 3 -fi - -############## - -# look for the solution that a project belongs to. -function find_solution_membership() -{ - local proj="$1"; shift - found_solution= - for i in ${SOLUTIONS[*]}; do - # secret sauce--don't match on any old reference to the file; we need - # it to be coming from the real project definition. - grep -i "$proj\"," "$i" &>/dev/null - if [ $? -eq 0 ]; then -#echo "$proj found in solution $i" - found_solution="$i" - break - fi - done -} - -function complain_about_project() -{ - filename="$1"; shift - echo "!!" - echo "Project $proj_base is in error (at $proj_file)" - echo "it references project $filename which is external to the solution." - echo "!!" - ((errors_seen++)) -} - -############## - -proj_base="$(basename $proj_file)" - -CHECKERS="$TMP/checking_refs.txt" - -errors_seen=0 - -#hmmm fix this for big time -export SOLUTIONS=("$BUILD_TOP/libraries/solutions/"*.sln "$BUILD_TOP/products/"*/*.sln) - -find_solution_membership "$proj_base" - -#echo found sol is $found_solution - -if [ -z "$found_solution" ]; then - echo error: could not find the solution containing $proj_base - exit 3 -fi - -# get all the project references from the project file being tested. -#hmmm: fix this path to extract! -bash "$BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh" "$proj_file" >"$CHECKERS" - -# iterate over all references in the project file. -while read line; do - grep -i "$line" "$found_solution" &>/dev/null - if [ $? -ne 0 ]; then - complain_about_project "$line" - fi -done <"$CHECKERS" - -if [ $errors_seen -eq 0 ]; then - echo "project $proj_base is clean." -else - echo "ERROR: there were $errors_seen problems in $proj_base; see above logging." - exit 3 -fi - - diff --git a/nucleus/.cproject b/nucleus/.cproject new file mode 100644 index 00000000..13aecb09 --- /dev/null +++ b/nucleus/.cproject @@ -0,0 +1,972 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +make + +all +true +true +true + + +make + +all +true +true +false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +make + +all +true +true +true + + +make + +all +true +true +false + + + + + + + + + diff --git a/nucleus/.project b/nucleus/.project new file mode 100644 index 00000000..6057985b --- /dev/null +++ b/nucleus/.project @@ -0,0 +1,82 @@ + + + hoople_core + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + ?name? + + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.autoBuildTarget + all + + + org.eclipse.cdt.make.core.buildArguments + + + + org.eclipse.cdt.make.core.buildCommand + make + + + org.eclipse.cdt.make.core.buildLocation + ${workspace_loc:/hoople_core/Debug} + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.contents + org.eclipse.cdt.make.core.activeConfigSettings + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.fullBuildTarget + all + + + org.eclipse.cdt.make.core.stopOnError + true + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + true + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/nucleus/applications/bookmark_tools/bookmark_tree.cpp b/nucleus/applications/bookmark_tools/bookmark_tree.cpp new file mode 100644 index 00000000..c8fc1f55 --- /dev/null +++ b/nucleus/applications/bookmark_tools/bookmark_tree.cpp @@ -0,0 +1,485 @@ +/*****************************************************************************\ +* * +* Name : bookmark_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///#include //temp + +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace nodes; +using namespace structures; +using namespace textual; + +#define DEBUG_MARKS + // uncomment to have more debugging noise, but a reasonable amount. +//#define DEBUG_MARKS_TREE + // uncomment to get crazy noisy debug noise about tree traversal. + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s) +#define SHOW_LINE a_sprintf("line %d: ", _line_number) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), SHOW_LINE + s) +#define DEADLY_LINE (astring(func) + a_sprintf(", line %d: ", _line_number)) + +const int ESTIMATED_ELEMENTS = 100; + // we're planning for about this many links to be efficiently handled. + +const int MAX_LINE_SIZE = 256 * KILOBYTE; + // the largest line we'll process in the links database. + +// used to compare two strings while ignoring case; we use this to find +// our categories in the symbol table. +bool case_insense_compare(const astring &a, const astring &b) +{ return a.iequals(b); } + +//////////////////////////////////////////////////////////////////////////// + +listo_links::listo_links() : amorph(), _next_index(0) {} + +void listo_links::add(link_record *new_rec, bool sort) +{ + // we don't sort blank lines--they just get dropped in the end of + // the section. + if (sort && new_rec->_description.t()) { + for (int i = _next_index; i < elements(); i++) { + const astring &desc_cur = borrow(i)->_description; +//this check doesn't make much sense; it only checks if the description is equal? +// if it were really sorting, wouldn't it need to check if the check is greater than current? + if (desc_cur.iequals(new_rec->_description) +// || shouldn't there be a case for this being greater than the current??? + || !desc_cur) { + insert(i + 1, 1); + put(i + 1, new_rec); + return; + } + } + } + append(new_rec); + if (!sort) + _next_index = elements(); +} + +//////////////////////////////////////////////////////////////////////////// + +class symbol_int : public symbol_table +{ +public: + symbol_int() : symbol_table(10) {} +}; + +//////////////////////////////////////////////////////////////////////////// + +bookmark_tree::bookmark_tree() +: _line_number(0), + _mark_tree(new inner_mark_tree("Root", 0, ESTIMATED_ELEMENTS)), + _link_count(0), + _category_count(0), + _last_parent(_mark_tree), + _last_node(_mark_tree), + _links_seen(new symbol_int), + _category_names(new string_table) +{} + +bookmark_tree::~bookmark_tree() +{ + WHACK(_links_seen); + WHACK(_mark_tree); + WHACK(_category_names); +} + +void bookmark_tree::break_name(const astring &to_break, astring &name, + astring &nick) +{ + nick = astring::empty_string(); + name = to_break; + int indy = name.find('['); + if (negative(indy)) return; + nick = name.substring(indy + 1, name.end()); + while ( (nick[nick.end()] == ' ') || (nick[nick.end()] == ']') ) + nick.zap(nick.end(), nick.end()); + name.zap(indy, name.end()); + name.strip_spaces(); + nick.strip_spaces(); +} + +inner_mark_tree &bookmark_tree::access_root() { return *_mark_tree; } + +bool bookmark_tree::magic_category_comparison(const astring &a, const astring &b) +{ +// FUNCDEF("magic_category_comparison"); +//LOG(astring("compare: a=") + a + " b=" + b); + if (a.iequals(b)) return true; + astring a_name, a_nick; + break_name(a, a_name, a_nick); + astring b_name, b_nick; + break_name(b, b_name, b_nick); + if (a_name.iequals(b_name)) return true; + if (a_nick.t() && a_nick.iequals(b_name)) return true; + if (b_nick.t() && a_name.iequals(b_nick)) return true; + if (a_nick.t() && b_nick.t() && a_nick.iequals(b_nick)) return true; + return false; +} + +const astring &HTTP_HEAD = "http://"; +const astring &FTP_HEAD = "ftp://"; +const astring &WWW_SITE = "www."; +const astring &FTP_SITE = "ftp."; + +bool bookmark_tree::advance(int &index, const astring &check, const astring &finding) +{ + if (check.compare(finding, index, 0, finding.length(), false)) { + index += finding.length(); + return true; + } + return false; +} + +int bookmark_tree::find_prune_point(const astring &to_prune) +{ + int to_return = 0; + advance(to_return, to_prune, HTTP_HEAD); + advance(to_return, to_prune, FTP_HEAD); + advance(to_return, to_prune, WWW_SITE); + advance(to_return, to_prune, FTP_SITE); + return to_return; +} + +astring bookmark_tree::prune_link_down(const astring &to_prune) +{ +//printf("%s\n", (astring("pruned=") + to_prune.substring(find_prune_point(to_prune), to_prune.end())).s()); + return to_prune.substring(find_prune_point(to_prune), to_prune.end()); } + +bool bookmark_tree::excellent_link_comparator(const astring &a, const astring &b) +{ + int prune_a = find_prune_point(a); + int prune_b = find_prune_point(b); + int bigger_len = maximum(a.length() - prune_a, b.length() - prune_b); + bool to_return = a.compare(b, prune_a, prune_b, bigger_len, false); +//if (to_return) printf("%s and %s are equal.", a.s(), b.s()); + return to_return; +} + +inner_mark_tree *bookmark_tree::find_parent(const astring &parent_name) +{ + FUNCDEF("find_parent"); + // first, look for the node above the last parent. + inner_mark_tree *parent = dynamic_cast + (_last_parent->find(parent_name, inner_mark_tree::recurse_upward, + magic_category_comparison)); + +#ifdef DEBUG_MARKS_TREE + if (parent) + LOG(astring("trying upwards find for parent node ") + parent_name); +#endif + + if (!parent) { +#ifdef DEBUG_MARKS_TREE + LOG(astring("upwards find failed seeking on last_parent node ") + + parent_name); +#endif + + // we didn't find the parent above the last category... + parent = dynamic_cast(_last_node->find(parent_name, + inner_mark_tree::recurse_upward, magic_category_comparison)); + } + + if (!parent) { +#ifdef DEBUG_MARKS_TREE + LOG(astring("upwards find failed seeking on last_node ") + parent_name); +#endif + + // last node didn't help either. + parent = dynamic_cast(_mark_tree->find(parent_name, + inner_mark_tree::recurse_downward, magic_category_comparison)); + } + if (!parent) { + // failed to find the parent node, so hook it to the root node. + LOG(astring("failed to find parent node ") + parent_name); + + // create a parent node and use it for this guy. + inner_mark_tree *new_node = new inner_mark_tree(parent_name, + _line_number, ESTIMATED_ELEMENTS); + _mark_tree->attach(new_node); + _mark_tree->sort(); + _category_count++; + + parent = new_node; + } else { +#ifdef DEBUG_MARKS_TREE + LOG(astring("found parent node ") + parent_name); +#endif + } + + return parent; +} + +inner_mark_tree *bookmark_tree::process_category(const string_array &items) +{ + FUNCDEF("process_category"); + const astring &category_name = items[1]; + const astring &parent_name = items[2]; + + if (items.length() > 3) { + // complain about a possibly malformed category. + LOG(astring("category ") + category_name + " under " + parent_name + + " has extra fields!"); + } + +//BASE_LOG("CURRENT:"); +//BASE_LOG(_mark_tree->text_form()); + + // make sure we don't add anything to the tree if this is the root. + if (!parent_name || magic_category_comparison("Root", category_name)) { +#ifdef DEBUG_MARKS_TREE + LOG(astring("skipping parent node for ") + category_name); +#endif + return _mark_tree; + } + + // ensure that the categories aren't competing with other names. + astring main_name, nickname; + break_name(category_name, main_name, nickname); + astring *found1 = _category_names->find(main_name, case_insense_compare); + astring *found2 = _category_names->find(nickname, case_insense_compare); + if (found1 || found2) { + astring catnames; + if (found1) catnames = *found1; // add the first match, if it exists. + if (found2) { + if (!!catnames) catnames += " and "; + catnames += *found2; + } + LOG(astring("category name \"") + category_name + + "\" in conflict with existing: " + catnames); + inner_mark_tree *fake_it = NIL; + +//hmmm: neither of these are right; they need to use a comparator that +// uses our magic comparison function. + + if (found1) { +#ifdef DEBUG_MARKS + LOG(astring("found existing category for main name: ") + main_name); +#endif +// fake_it = (inner_mark_tree *)_mark_tree->find(*found1, +// symbol_tree::recurse_downward); + fake_it = dynamic_cast(_mark_tree->find + (*found1, inner_mark_tree::recurse_downward, + magic_category_comparison)); + } + if (fake_it) { +#ifdef DEBUG_MARKS + LOG(astring("returning existing category for main name: ") + main_name + + " as: " + fake_it->name()); +#endif + return fake_it; + } + if (found2) { +#ifdef DEBUG_MARKS + LOG(astring("found existing category for nickname: ") + nickname); +#endif +/// fake_it = (inner_mark_tree *)_mark_tree->find(*found2, +/// symbol_tree::recurse_downward); + fake_it = dynamic_cast(_mark_tree->find + (*found2, inner_mark_tree::recurse_downward, + magic_category_comparison)); + } + if (fake_it) { +#ifdef DEBUG_MARKS + LOG(astring("returning existing category for nickname: ") + nickname + + " as: " + fake_it->name()); +#endif + return fake_it; + } + LOG("==> failure to find a match for either category!"); + deadly_error(class_name(), func, "collision resolution code failed; " + "please fix category error"); + return NIL; + } + // now that we know these names are unique, we'll add them into the list + // so future categories can't reuse these. + _category_names->add(main_name, main_name); + if (!!nickname) _category_names->add(nickname, nickname); + + inner_mark_tree *parent = find_parent(parent_name); + _last_parent = parent; // set the parent for the next time. + + // see if the category is already present under the parent. + for (int i = 0; i < parent->branches(); i++) { + inner_mark_tree *curr = dynamic_cast(parent->branch(i)); + if (!curr) + non_continuable_error(class_name(), DEADLY_LINE, "missing branch in tree"); +#ifdef DEBUG_MARKS_TREE + LOG(astring("looking at branch ") + curr->name()); +#endif + if (magic_category_comparison(curr->name(), category_name)) { + // it already exists? argh. + LOG(astring("category ") + category_name + " already exists under " + + parent_name + "."); + _last_node = curr; + return curr; + } + } + + inner_mark_tree *new_node = new inner_mark_tree(category_name, + _line_number, ESTIMATED_ELEMENTS); + parent->attach(new_node); + parent->sort(); + _last_node = new_node; + + _category_count++; + +#ifdef DEBUG_MARKS_TREE + LOG(astring("attaching node ") + category_name + " to parent " + + parent->name()); +#endif + return new_node; +} + +void bookmark_tree::process_link(const string_array &items) +{ + FUNCDEF("process_link"); + astring description = items[1]; + astring parent_name = items[2]; + astring url = "UNKNOWN"; + if (items.length() >= 4) url = items[3]; + + // strip any directory slashes that are provided as a suffix. we don't need + // them and they tend to confuse the issue when we look for duplicates. + while (url[url.end()] == '/') { + url.zap(url.end(), url.end()); + } + + // make some noise if they seem to have a badly formed link. + if (items.length() < 4) { + LOG(astring("link ") + description + " under " + parent_name + + " has no URL!"); + } else if (items.length() > 4) { + LOG(astring("link ") + description + " under " + parent_name + + " has extra fields!"); + } + + // find the parent for this link. + inner_mark_tree *parent = find_parent(parent_name); + _last_parent = parent; // set the parent for the next time. + + // see if the link already exists. + int *found = _links_seen->find(url, excellent_link_comparator); + if (found) { + // this is not so great; a duplicate link has been found. + LOG(a_sprintf("Duplicate Link: line %d already has ", *found) + url); + return; + } else { + _links_seen->add(prune_link_down(url), _line_number); + } + + // add the link to the parent. + link_record *new_rec = new link_record(description, url, _line_number); + parent->_links.add(new_rec); + + _link_count++; +} + +void bookmark_tree::process_comment(const astring ¤t_line_in) +{ +/// FUNCDEF("process_comment"); + astring current_line = current_line_in; + + // output the comment as simple text. +//BASE_LOG("comment case"); + if (current_line.contains("http:")) { + astring hold_curr = current_line; + int indy = current_line.find("http:"); + hold_curr.zap(0, indy - 1); + current_line = astring("        " + "" + hold_curr + ""; + } else if (current_line.t()) { + // snap the comment character off of the front. + current_line.zap(0, 0); + } + + link_record *new_rec = new link_record(current_line, + astring::empty_string(), _line_number); + _last_node->_links.add(new_rec, false); +} + +int bookmark_tree::read_csv_file(const astring &input_filename) +{ + FUNCDEF("read_csv_file"); + byte_filer input_file(input_filename, "r"); + if (!input_file.good()) + non_continuable_error(class_name(), DEADLY_LINE, + "the input file could not be opened"); + + string_array items; // parsed in csv line. + astring current_line; // read from input file. + + // read the lines in the file, one at a time. + while (input_file.getline(current_line, MAX_LINE_SIZE) > 0) { + _line_number++; // go to the next line. + // remove the carriage returns first. + while (parser_bits::is_eol(current_line[current_line.end()])) { + current_line.zap(current_line.end(), current_line.end()); + } + current_line.strip_spaces(); + if (!current_line.length()) { +// // blank lines get treated as a special case. they are always added +// // at the end of the list. +// process_comment(current_line); + continue; + } else if (current_line[0] == '#') { + // handle a comment in the database. + process_comment(current_line); + } else { + // csv parse the line, since we don't support much else. + bool parsed = list_parsing::parse_csv_line(current_line, items); + if (!parsed) + non_continuable_error(class_name(), DEADLY_LINE, + astring("the line could not be parsed: ") + current_line); + if (!items.length()) { + LOG("bad formatting on this line."); + continue; + } + if (items[0].iequals("C")) { + inner_mark_tree *node = process_category(items); + if (!node) { + LOG(astring("failed to get a node for ") + items[1]); + } + } else if (items[0].iequals("L")) { + process_link(items); + } else { + non_continuable_error(class_name(), DEADLY_LINE, + astring("unknown format in line: ") + current_line); + } + } + } + return 0; +} + diff --git a/nucleus/applications/bookmark_tools/bookmark_tree.h b/nucleus/applications/bookmark_tools/bookmark_tree.h new file mode 100644 index 00000000..744d9fd3 --- /dev/null +++ b/nucleus/applications/bookmark_tools/bookmark_tree.h @@ -0,0 +1,144 @@ +#ifndef BOOKMARK_TREE_CLASS +#define BOOKMARK_TREE_CLASS + +/*****************************************************************************\ +* * +* Name : bookmark_tree * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Parses a link database in HOOPLE format into tree structure. * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +// forward. +class inner_mark_tree; +class link_record; +class listo_links; +class symbol_int; + +//////////////////////////////////////////////////////////////////////////// + +class bookmark_tree +{ +public: + bookmark_tree(); + virtual ~bookmark_tree(); + DEFINE_CLASS_NAME("bookmark_tree"); + + int read_csv_file(const basis::astring &input_filename); + // reads the file contents of "input_filename" into this tree. + + static void break_name(const basis::astring &to_break, basis::astring &name, + basis::astring &nick); + // breaks a category name into the two components, if they exist. + + static bool magic_category_comparison(const basis::astring &a, const basis::astring &b); + // compares the two strings "a" and "b" and returns true if either the + // main name or the nickname matches either. + + static basis::astring prune_link_down(const basis::astring &to_prune); + // reduces a URL to its bare bones. it will strip out the "http://" and "www." and such. + + static bool excellent_link_comparator(const basis::astring &a, const basis::astring &b); + // a string comparator that handles how links are often formed. it uses the link pruner + // to decide whether the links are equal at their root. + + inner_mark_tree *process_category(const structures::string_array &items); + // handles category declarations and adds the new category to our list. + // this tries to do the intelligent thing if the category is already + // found to exist, meaning that the file has a duplicate category + // definitions. + + void process_link(const structures::string_array &items); + + void process_comment(const basis::astring ¤t_line_in); + + inner_mark_tree *find_parent(const basis::astring &parent_name); + // locates the parent called "parent_name" given the context that + // we've saved about the last parent. + + static bool advance(int &index, const basis::astring &check, const basis::astring &finding); + //!< moves the "index" forward if the "finding" string is the head of "check". + + static int find_prune_point(const basis::astring &to_prune); + //!< attempts to locate the real start of the root URL in "to_prune". + + // these provide access to the information held about the tree... + + inner_mark_tree &access_root(); // allows access to the root of the tree. + + int link_count() const { return _link_count; } + + int category_count() const { return _category_count; } + +// public data members... currently this is used outside the class. + int _line_number; // the current line in the database. + +private: + inner_mark_tree *_mark_tree; // our tree of categories. + int _link_count; // number of links. + int _category_count; // number of categories. + inner_mark_tree *_last_parent; // the last parent we saw. + inner_mark_tree *_last_node; // the last node we touched. + symbol_int *_links_seen; // URLs we've seen. + structures::string_table *_category_names; // used to enforce uniqueness of categories. +}; + +//////////////////////////////////////////////////////////////////////////// + +class link_record +{ +public: + basis::astring _description; + basis::astring _url; + int _uid; + + link_record(const basis::astring &description, const basis::astring &url, int uid) + : _description(description), _url(url), _uid(uid) {} +}; + +//////////////////////////////////////////////////////////////////////////// + +class listo_links : public structures::amorph +{ +public: + listo_links(); + + void add(link_record *new_rec, bool sort = true); + +private: + int _next_index; // tracks where we've added unsorted items. +}; + +//////////////////////////////////////////////////////////////////////////// + +class inner_mark_tree : public nodes::symbol_tree +{ +public: + listo_links _links; // the list held at this node. + int _uid; // the unique identifier of this node. + + inner_mark_tree(const basis::astring &node_name, int uid, int max_bits = 2) + : nodes::symbol_tree(node_name, max_bits), _uid(uid) {} + +}; + +//////////////////////////////////////////////////////////////////////////// + +#endif + diff --git a/nucleus/applications/bookmark_tools/example_crash_file_for_marks.csv b/nucleus/applications/bookmark_tools/example_crash_file_for_marks.csv new file mode 100644 index 00000000..3bbff089 --- /dev/null +++ b/nucleus/applications/bookmark_tools/example_crash_file_for_marks.csv @@ -0,0 +1,23 @@ +"L","Shawn's Occultism and Religion Resources","Eclectic","http://valen.dws.acs.cmu.edu/occult/" +"L","Software, Unix and Music - Paladin Corporation","Research","http://www.paladincorp.com.au/" +"L","southwest indian foundation","unsorted","http://www.southwestindia.com" +"L","Sparptoe.com CD's","Music Archives","http://www.sharptoe.com/toe/cds.htm" +"L","Starbase - Formerly Premia Corporation","Tools","http://www.premia.com/" +"L","superpages.com's BigBook","People","http://bigbook.com/" +"L","Technische Universität Darmstadt","schools","http://www.th-darmstadt.de/" +"L","The Art Bell Web Site","Radio","http://www.artbell.com/" +"L","The iBrator.","Cyber","http://www.briarskin.com/humor/ibrator/" +"L","The Louvre","museums","http://louvre.edu" +"L","The Morrígan's WebAerie","Celtic","http://morrigan.alabanza.com/" +"L","The Net Censorship Dilemma: Liberty or Tyranny","Legalizing Freedom","http://rene.efa.org.au/liberty/" +"L","TombTown home page","web","http://www.tombtown.com/" +"L","Tom Valesky's Old Old Home On the Wild, Wild Worldwide Web","Thomas Valesky","http://www.site.gmu.edu/tvalesky/" +"L","Tuning Apache for Performance","apache","http://php.weblogs.com/tuning_apache_unix" +"L","VNC - Virtual Network Computing from ATT Laboratories Cambridge","ent_man","http://www.uk.research.att.com/vnc/" +"L","Welcome to Caligari Creator of trueSpace","Tools","http://www.caligari.com/" +"L","Welcome to QGPC Home Page","Chemicals","http://www.qgpc.com.qa/" +"L","Welcome to the Inquirer","papers","http://www.theinquirer.net/" +"L","Welcome To The Sequoia Bison Society!","Herd","http://www.sequoiabison.org/" +"L","Welcome to Virtual Pooh Sticks","Automata","http://pooh.muscat.co.uk/pooh-sticks/" +"L","Welcome to Webcorp Multimedia!","Sounds","http://www.webcorp.com/sounds/" +"L","Welcome to web.life.sucks","Nauseating","http://www.alt-life-sucks.org/" diff --git a/nucleus/applications/bookmark_tools/js_marks_maker.cpp b/nucleus/applications/bookmark_tools/js_marks_maker.cpp new file mode 100644 index 00000000..d32f50f4 --- /dev/null +++ b/nucleus/applications/bookmark_tools/js_marks_maker.cpp @@ -0,0 +1,347 @@ +/*****************************************************************************\ +* * +* Name : marks_maker_javascript * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Turns a link database in HOOPLE format into a web page, when given a * +* suitable template file. The template file must have the phrase: * +* $INSERT_LINKS_HERE * +* at the point where the generated links are supposed to be stored. * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace nodes; +using namespace structures; +using namespace textual; +using namespace timely; + +//#define DEBUG_MARKS + // uncomment to have more debugging noise. + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ + a_sprintf("line %d: ", _categories._line_number) + s, ALWAYS_PRINT) + +const int MAX_FILE_SIZE = 4 * MEGABYTE; + // the largest file we'll read. + +//////////////////////////////////////////////////////////////////////////// + +class marks_maker_javascript : public application_shell +{ +public: + marks_maker_javascript() : application_shell(), _need_closure(false), + _loader_count(0), _link_spool(0), _functions_pending(0) {} + DEFINE_CLASS_NAME("marks_maker_javascript"); + virtual int execute(); + int print_instructions(const filename &program_name); + + int write_marks_page(const astring &output_filename, + const astring &template_filename); + // given a tree of links, this writes out a web page to "output_filename" + // using a template file "template_filename". + +private: + bookmark_tree _categories; // our tree of categories. + bool _need_closure; // true if our
    needs a closure. + int _loader_count; // count of the loader functions. + int _link_spool; // count of which link we're writing. + stack _functions_pending; // used for javascript node functions. + +//this needs to gather any strings that would have gone into functions. +//instead, they need to be written into the current node's string. +//when a new function def would be seen, then we need to push a new node +//for accumulating the text. + + // these handle outputting text for categories and links. + void write_category(inner_mark_tree *node, astring &output); + void write_link(inner_mark_tree *node, const link_record &linko, + astring &output); +}; + +//////////////////////////////////////////////////////////////////////////// + +int marks_maker_javascript::print_instructions(const filename &program_name) +{ + a_sprintf to_show("%s:\n\ +This program needs three filenames as command line parameters. The -i flag\n\ +is used to specify the input filename, the -t flag specifies a template web\n\ +page which is used as the wrapper around the links, and the -o flag specifies\n\ +the web page to be created. The input file is expected to be in the HOOPLE\n\ +link database format. The output file will be created from the template file\n\ +by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\ +formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\ +will be replaced with the date when the output file is regenerated.\n\ +The HOOPLE link format is documented here:\n\ + http://hoople.org/guides/link_database/format_manifesto.txt\n\ +", program_name.basename().raw().s(), program_name.basename().raw().s()); + program_wide_logger::get().log(to_show, ALWAYS_PRINT); + return 12; +} + +void marks_maker_javascript::write_category(inner_mark_tree *node, astring &output) +{ +// FUNCDEF("write_category"); + // output a javascript line for the category. + + int node_num = node->_uid; + inner_mark_tree *parent = dynamic_cast(node->parent()); + int parent_node = parent? parent->_uid : -1; + // the parent node for root is a negative one. + astring chewed_name = node->name(); + for (int i = chewed_name.end(); i >= 0; i--) { + // escape any raw single quotes that we see. + if (chewed_name[i] == '\'') { + chewed_name.zap(i, i); + chewed_name.insert(i, "\\'"); + } + } + output += a_sprintf(" b.add(%d, %d, '%s');\n", node_num, parent_node, + chewed_name.s()); +} + +void marks_maker_javascript::write_link(inner_mark_tree *node, const link_record &linko, + astring &output) +{ +// FUNCDEF("write_link"); + // write a javascript link definition. + int parent_node = node->_uid; + astring chewed_name = linko._description; + for (int i = chewed_name.end(); i >= 0; i--) { + // escape any raw single quotes that we see. + if (chewed_name[i] == '\'') { + chewed_name.zap(i, i); + chewed_name.insert(i, "\\'"); + } + } + + if (!linko._url) { + // this just appears to be a comment line. + if (!linko._description) return; // it's a nothing line. + +/* +//hmmm: probably not what we want. +//hmmm: why not, again? + output += linko._description; + output += "
    "; + output += parser_bits::platform_eol_to_chars(); +*/ + return; + } + + // generate a function header if the number of links is a multiple of 100. + if (! (_link_spool % 100) ) { + if (_link_spool) { + // close out the previous function and set a timeout. + output += " setTimeout('run_tree_loaders()', 0);\n"; + output += "}\n"; + } + + output += a_sprintf("function tree_loader_%d() {\n", _loader_count++); + } + _link_spool++; + + output += a_sprintf(" b.add(%d, %d, '%s', '%s');\n", + linko._uid, parent_node, chewed_name.s(), linko._url.s()); +} + +int marks_maker_javascript::execute() +{ +// FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + + command_line cmds(_global_argc, _global_argv); // process the command line parameters. + astring input_filename; // we'll store our link database name here. + astring output_filename; // where the web page we're creating goes. + astring template_filename; // the wrapper html code that we'll stuff. + if (!cmds.get_value('i', input_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('o', output_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('t', template_filename, false)) + return print_instructions(cmds.program_name()); + + BASE_LOG(astring("input file: ") + input_filename); + BASE_LOG(astring("output file: ") + output_filename); + BASE_LOG(astring("template file: ") + template_filename); + + int ret = _categories.read_csv_file(input_filename); + if (ret) return ret; + + ret = write_marks_page(output_filename, template_filename); + if (ret) return ret; + + return 0; +} + +int marks_maker_javascript::write_marks_page(const astring &output_filename, + const astring &template_filename) +{ + FUNCDEF("write_marks_page"); + astring long_string; + // this is our accumulator of links. it is the semi-final result that will + // be injected into the template file. + + // add our target layer so that we can write to a useful place. + long_string += "
    Marks Target
    \n"; + + // add the tree style and creation of the tree object into the text. + long_string += "\n
    \n"; + long_string += "\n"; + long_string += "

    open all | " + "close all

    \n"; + long_string += "
    \n"; + + byte_filer template_file(template_filename, "r"); + astring full_template; + if (!template_file.good()) + non_continuable_error(class_name(), func, "the template file could not be opened"); + template_file.read(full_template, MAX_FILE_SIZE); + template_file.close(); + + // javascript output needs some extra junk added to the header section. + int indy = full_template.ifind(""); + if (negative(indy)) + non_continuable_error(class_name(), func, "the template file is missing " + "a declaration"); +//hmmm: the path here must be configurable! + full_template.insert(indy + 8, "\n\n" + "\n" + "\n"); + + // replace the tag with the long string we created. + bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string); + if (!found_it) + non_continuable_error(class_name(), func, "the template file is missing " + "the insertion point"); + full_template.replace("$TODAYS_DATE", time_stamp::notarize(true)); + + filename outname(output_filename); + if (outname.exists()) { + non_continuable_error(class_name(), func, astring("the output file ") + + output_filename + " already exists. It would be over-written if " + "we continued."); + } + + byte_filer output_file(output_filename, "w"); + if (!output_file.good()) + non_continuable_error(class_name(), func, "the output file could not be opened"); + // write the newly generated web page out now. + output_file.write(full_template); + output_file.close(); + +// show the tree. +// BASE_LOG(""); +// BASE_LOG(_categories.access_root().text_form()); + + BASE_LOG(a_sprintf("wrote %d links in %d categories.", + _categories.link_count(), _categories.category_count())); + BASE_LOG(astring("")); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(marks_maker_javascript, ) + diff --git a/nucleus/applications/bookmark_tools/link_parser.cpp b/nucleus/applications/bookmark_tools/link_parser.cpp new file mode 100644 index 00000000..84467b5e --- /dev/null +++ b/nucleus/applications/bookmark_tools/link_parser.cpp @@ -0,0 +1,518 @@ +/*****************************************************************************\ +* * +* Name : link_parser * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Processes html files and finds the links. A database in the HOOPLE * +* link format is created from the links found. * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// Notes: +// +// the standard link structure in html is similar to this: +// Link Name and Launching Point +// +// the standard we adopt for section titles is that it must be a heading +// marker. that formatting looks like this, for example: +//

    The Section Title:

    + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +//#define DEBUG_LINK_PARSER + // uncomment for noisier run to seek problems. + +//////////////////////////////////////////////////////////////////////////// + +const int MAX_FILE_SIZE = 4 * MEGABYTE; + // this is the largest html file size we will process. + +//////////////////////////////////////////////////////////////////////////// + +// a macro that increments the position in the string and restarts the loop. +#define INCREM_N_GO { curr_index++; continue; } + +// puts the current character on the intermediate string. +#define ADD_INTERMEDIATE { \ + char add_in = full_contents[curr_index]; \ + if ( (add_in == '<') || (add_in == '>') ) { \ + add_in = '-'; \ + } \ + intermediate_text += add_in; \ +} + +// returns a character in lower-case, if 'a' is in upper case. +char normalize_char(char a) +{ + if ( (a >= 'A') && (a <= 'Z') ) return a + 'a' - 'A'; + return a; +} + +// returns true if the two characters are the same, ignoring upper/lower case. +bool caseless_equals(char a, char b) { return normalize_char(a) == normalize_char(b); } + +// a macro that skips all characters until the specified one is seen. +#define JUMP_TO_CHAR(to_find, save_them) { \ + while ( (curr_index < full_contents.length()) \ + && !caseless_equals(to_find, full_contents[curr_index]) ) { \ + if (save_them) ADD_INTERMEDIATE; \ + curr_index++; \ + } \ +} + +// increments the state, the current character and restarts the loop. +#define NEXT_STATE_INCREM { \ + state = parsing_states(state+1); /* move forward in states. */ \ + curr_index++; \ + continue; \ +} + +// cleans out the disallowed characters in the string provided. +#define CLEAN_UP_NAUGHTY(s) { \ + while (s.replace("\n", " ")) {} \ + while (s.replace("\r", "")) {} \ + s.strip_spaces(); \ +} + +//was before the strip_spaces code above. +/* + int indy = s.find("--"); \ + while (non_negative(indy)) { \ + s[indy] = ' '; / * replace the first dash with a space. * / \ + for (int i = indy + 1; i < s.length(); i++) { \ + if (s[i] != '-') break; \ + s.zap(i, i); \ + i--; \ + } \ + indy = s.find("--"); \ + } \ + while (s.replace(" ", " ")) {} \ +*/ + +// cleans up underscores in areas that are supposed to be english. +#define MAKE_MORE_ENGLISH(s) \ + s.replace_all('_', ' ') + +void strain_out_html_codes(astring &to_edit) +{ + for (int i = 0; i < to_edit.length(); i++) { + if (to_edit[i] != '<') continue; + // found a left bracket. + int indy = to_edit.find('>', i); + if (negative(indy)) return; // bail out, unexpected unmatched bracket. + to_edit.zap(i, indy); + i--; // skip back to reconsider current place. + } +} + +// writes out the currently accumulated link info. +#define WRITE_LINK { \ + /* clean naughty characters out of the names. */ \ + CLEAN_UP_NAUGHTY(url_string); \ + CLEAN_UP_NAUGHTY(name_string); \ + /* output a link in the HOOPLE format. */ \ + astring to_write = "\"L\",\""; \ + to_write += translate_web_chars(name_string); \ + to_write += "\",\""; \ + to_write += abbreviate_category(last_heading); \ + to_write += "\",\""; \ + to_write += translate_web_chars(url_string); \ + to_write += "\"\n"; \ + output_file.write(to_write); \ + _link_count++; \ +} +//was after second clean up naughty +/*argh yuck... if (url_string.ends(name_string)) { \ + / * handle the name being boring. replace with the intermediate text. * / \ + MAKE_MORE_ENGLISH(intermediate_text); \ + strain_out_html_codes(intermediate_text); \ + CLEAN_UP_NAUGHTY(intermediate_text); \ + if (intermediate_text.length()) \ + name_string = intermediate_text; \ + } \ +*/ + +// writes out the current section in the HOOPLE format. +// currently the parent category is set to Root. +#define WRITE_SECTION { \ + CLEAN_UP_NAUGHTY(last_heading); /* clean the name. */ \ + /* output a category definition. */ \ + astring to_write = "\"C\",\""; \ + to_write += translate_web_chars(last_heading); \ + to_write += "\",\""; \ + to_write += abbreviate_category(last_parents.top()); \ + to_write += "\"\n"; \ + output_file.write(to_write); \ + _category_count++; \ +} + +// clears our accumulator strings. +#define RESET_STRINGS { \ + url_string = astring::empty_string(); \ + name_string = astring::empty_string(); \ + intermediate_text = astring::empty_string(); \ +} + +//////////////////////////////////////////////////////////////////////////// + +class link_parser : public application_shell +{ +public: + link_parser(); + DEFINE_CLASS_NAME("link_parser"); + virtual int execute(); + int print_instructions(const filename &program_name); + +private: + int _link_count; // number of links. + int _category_count; // number of categories. + + astring url_string; // the URL we've parsed. + astring name_string; // the name that we've parsed for the URL. + astring last_heading; // the last name that was set for a section. + stack last_parents; // the history of the parent names. + astring intermediate_text; // strings we saw before a link. + + astring heading_num; + // this string form of a number tracks what kind of heading was started. + + astring abbreviate_category(const astring &simplify); + // returns the inner category nickname if the category has one. + + astring translate_web_chars(const astring &vervoom); + // translates a few web chars that are safe for csv back into their non-encoded form. +}; + +//////////////////////////////////////////////////////////////////////////// + +link_parser::link_parser() +: application_shell(), + _link_count(0), + _category_count(0), + last_heading("Root"), + last_parents() +{ + last_parents.push(last_heading); // make sure we have at least one level. +} + +int link_parser::print_instructions(const filename &program_name) +{ + a_sprintf to_show("%s:\n\ +This program needs two filenames as command line parameters. The -i flag\n\ +is used to specify the input filename and the -o flag specifies the output\n\ +file to be created. The input file is expected to be an html file\n\ +containing links to assorted web sites. The links are gathered, along with\n\ +descriptive text that happens to be near them, to create a link database in\n\ +the HOOPLE link format and write it to the output file. HOOPLE link format\n\ +is basically a CSV file that defines the columns 1-4 for describing either\n\ +link categories (which support hierarchies) or actual links (i.e., URLs of\n\ +interest). The links are written to a CSV file in the standard HOOPLE link\n\ +The HOOPLE link format is documented here:\n\ + http://hoople.org/guides/link_database/format_manifesto.txt\n\ +", program_name.basename().raw().s(), program_name.basename().raw().s()); + program_wide_logger::get().log(to_show, ALWAYS_PRINT); + return 12; +} + +astring link_parser::abbreviate_category(const astring &simplify) +{ + astring to_return; + astring name_portion; + bookmark_tree::break_name(simplify, name_portion, to_return); + if (!to_return) return name_portion; + return to_return; +} + +astring link_parser::translate_web_chars(const astring &vervoom) +{ + astring to_return = vervoom; + to_return.replace_all("&", "&"); + to_return.replace_all("ä", "ä"); + to_return.replace_all("©", "(c)"); + to_return.replace_all("é", "é"); + to_return.replace_all("«", "--"); + to_return.replace_all("‘", "'"); + to_return.replace_all("“", "'"); + to_return.replace_all("—", "--"); + to_return.replace_all("–", "--"); + to_return.replace_all(" ", " "); + to_return.replace_all("»", "--"); + to_return.replace_all("”", "'"); + to_return.replace_all("’", "'"); + + to_return.replace_all("%7E", "~"); + to_return.replace_all("%28", "("); + to_return.replace_all("%29", ")"); + return to_return; +} + +int link_parser::execute() +{ + FUNCDEF("main"); + command_line cmds(_global_argc, _global_argv); // process the command line parameters. + astring input_filename; // we'll store our bookmarks file's name here. + astring output_filename; // where the processed marks go. + if (!cmds.get_value('i', input_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('o', output_filename, false)) + return print_instructions(cmds.program_name()); + + BASE_LOG(astring("input file: ") + input_filename); + BASE_LOG(astring("output file: ") + output_filename); + + astring full_contents; + byte_filer input_file(input_filename, "r"); + if (!input_file.good()) + non_continuable_error(class_name(), func, "the input file could not be opened"); + input_file.read(full_contents, MAX_FILE_SIZE); + input_file.close(); + + filename outname(output_filename); + if (outname.exists()) { + non_continuable_error(class_name(), func, astring("the output file ") + + output_filename + " already exists. It would be over-written if " + "we continued."); + } + + byte_filer output_file(output_filename, "w"); + if (!output_file.good()) + non_continuable_error(class_name(), func, "the output file could not be opened"); + + enum parsing_states { + // the states below are order dependent; do not change the ordering! + SEEKING_LINK_START, // looking for the beginning of an html link. + SEEKING_HREF, // finding the href portion of the link. + GETTING_URL, // chowing on the URL portion of the link. + SEEKING_NAME, // finding the closing bracket of the . + GETTING_NAME, // chowing down on characters in the link's name. + SEEKING_CLOSURE, // looking for the to end the link. + // there is a discontinuity after SEEKING_CLOSURE, but then the following + // states are also order dependent. + SAW_TITLE_START, // the beginning of a section heading was seen. + GETTING_TITLE, // grabbing characters in the title. + // new text processing states. + SAW_NESTING_INCREASE, // a new nesting level has been achieved. + SAW_NESTING_DECREASE, // we exited from a former nesting level. + }; + + int curr_index = 0; + parsing_states state = SEEKING_LINK_START; + while (curr_index < full_contents.length()) { + switch (state) { + case SEEKING_LINK_START: + // if we don't see a less-than, then it's not the start of html code, + // so we'll ignore it for now. + if (full_contents[curr_index] != '<') { + ADD_INTERMEDIATE; + INCREM_N_GO; + } + // found a left angle bracket, so now we need to decided where to go next for parsing + // the html coming up. + curr_index++; + // see if this is a heading. if so, we can snag the heading name. + if (caseless_equals('h', full_contents[curr_index])) { +#ifdef DEBUG_LINK_PARSER + LOG("into the '= '0') && (next <= '9') ) { + // we found our proper character for starting a heading. we need + // to jump into that state now. we'll leave the cursor at the + // beginning of the number. + state = SAW_TITLE_START; + INCREM_N_GO; + } + } + // check if they're telling us a new indentation level of the type we care about. + if (caseless_equals('d', full_contents[curr_index])) { +#ifdef DEBUG_LINK_PARSER + LOG("into the ' tag. + char next = full_contents[curr_index + 1]; + if (caseless_equals(next, 'l')) { +#ifdef DEBUG_LINK_PARSER + LOG("into the ' tag. + if ( caseless_equals(full_contents[curr_index + 1], 'd') + && caseless_equals(full_contents[curr_index + 2], 'l') ) { +#ifdef DEBUG_LINK_PARSER + LOG("into the '', false); + continue; + } +#ifdef DEBUG_LINK_PARSER + LOG("into the final case, the '', false); + continue; + } + // this looks like an address so find the start of the href. + NEXT_STATE_INCREM; + break; + case SAW_NESTING_INCREASE: + last_parents.push(last_heading); +#ifdef DEBUG_LINK_PARSER + LOG(a_sprintf("nesting inwards, new depth %d", last_parents.size())); +#endif + JUMP_TO_CHAR('>', false); + state = SEEKING_LINK_START; + break; + case SAW_NESTING_DECREASE: + last_parents.pop(); +#ifdef DEBUG_LINK_PARSER + LOG(a_sprintf("nesting outwards, new depth %d", last_parents.size())); +#endif + JUMP_TO_CHAR('>', false); + state = SEEKING_LINK_START; + break; + case SEEKING_HREF: + JUMP_TO_CHAR('h', false); // find the next 'h' for "href". + curr_index++; + if (!caseless_equals('r', full_contents[curr_index])) continue; + curr_index++; + if (!caseless_equals('e', full_contents[curr_index])) continue; + curr_index++; + if (!caseless_equals('f', full_contents[curr_index])) continue; + curr_index++; + if (full_contents[curr_index] != '=') continue; + curr_index++; + if (full_contents[curr_index] != '"') continue; + // whew, got through the word href and the assignment. the rest + // should all be part of the link. + NEXT_STATE_INCREM; + break; + case GETTING_URL: + // as long as we don't see the closure of the quoted string for the + // href, then we can keep accumulating characters from it. + if (full_contents[curr_index] == '"') NEXT_STATE_INCREM; + url_string += full_contents[curr_index]; + INCREM_N_GO; // keep chewing on it in this same state. + break; + case SEEKING_NAME: + JUMP_TO_CHAR('>', false); // find closing bracket. + NEXT_STATE_INCREM; // now start grabbing the name characters. + break; + case GETTING_NAME: + // we have to stop grabbing name characters when we spy a new code + // being started. + if (full_contents[curr_index] == '<') { + // if we see a closing command, then we assume it's the one we want. + if (full_contents[curr_index + 1] == '/') + NEXT_STATE_INCREM; + // if we see html inside the name, we just throw it out. + JUMP_TO_CHAR('>', false); + curr_index++; + continue; + } + name_string += full_contents[curr_index]; + INCREM_N_GO; // keep chewing on it in this same state. + break; + case SEEKING_CLOSURE: + JUMP_TO_CHAR('>', false); // find the closure of the html code. + // write the link out now. + WRITE_LINK; + // clean out our accumulated strings. + RESET_STRINGS; + state = SEEKING_LINK_START; + INCREM_N_GO; + break; + case SAW_TITLE_START: + heading_num = full_contents.substring(curr_index, curr_index); + JUMP_TO_CHAR('>', false); + NEXT_STATE_INCREM; // start eating the name. + break; + case GETTING_TITLE: { + int indy = full_contents.find('<', curr_index); + if (negative(indy)) { + state = SEEKING_LINK_START; // too weird, go back to start. + continue; + } + // push the last title if it differs from the top of the stack. + last_heading = full_contents.substring(curr_index, indy - 1); + WRITE_SECTION; + JUMP_TO_CHAR('<', false); // now find the start of the header closure. + JUMP_TO_CHAR('>', false); // now find the end of the header closure. + RESET_STRINGS; + state = SEEKING_LINK_START; // successfully found section name. + break; + } + default: + non_continuable_error(class_name(), func, "entered erroneous state!"); + } + } + + if (url_string.t()) WRITE_LINK; + + output_file.close(); + + BASE_LOG(a_sprintf("wrote %d links in %d categories.", _link_count, + _category_count)); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(link_parser, ) + diff --git a/nucleus/applications/bookmark_tools/makefile b/nucleus/applications/bookmark_tools/makefile new file mode 100644 index 00000000..655c9373 --- /dev/null +++ b/nucleus/applications/bookmark_tools/makefile @@ -0,0 +1,14 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +PROJECT = bookmark_tools +TYPE = application +SOURCE = bookmark_tree.cpp bookmark_version.rc +LOCAL_LIBS_USED += application configuration filesystem loggers mathematics nodes \ + processes textual timely structures basis +TARGETS = js_marks_maker.exe link_parser.exe marks_checker.exe marks_maker.exe marks_sorter.exe +USE_CURL = t + +include cpp/rules.def + diff --git a/nucleus/applications/bookmark_tools/marks_checker.cpp b/nucleus/applications/bookmark_tools/marks_checker.cpp new file mode 100644 index 00000000..1e2c6f07 --- /dev/null +++ b/nucleus/applications/bookmark_tools/marks_checker.cpp @@ -0,0 +1,453 @@ +/*****************************************************************************\ +* * +* Name : marks_checker * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Checks on the existence of the links listed in a HOOPLE format link * +* database and reports the bad ones. * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace algorithms; +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace nodes; +using namespace mathematics; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; + +//#define DEBUG_MARKS + // uncomment to have more debugging noise. + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(astring(s), ALWAYS_PRINT) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ + a_sprintf("line %d: ", _categories._line_number) + s) + +const int PAUSEY_SNOOZE = 200; + // how long we sleep if there are too many threads running already. + +const int MAXIMUM_THREADS = 14; + // we allow this many simultaneous web checks at a time. + +const int MAXIMUM_READ = 1008; + // we only download this much of the link. this avoids huge downloads of + // very large sites. + +const int MAXIMUM_ATTEMPTS = 2; + // we'll retry the check if we get an actual error instead of an http error + // code. when a name can't be found in the DNS, it sometimes comes back + // shortly after it was checked. if we see we can't reach the domain after + // this many tries, then we give up on the address. + +const int TIME_PER_REQUEST_IN_SEC = 60 * 6; + // limit our requests to this long of a period. then we will not be + // stalled forever by uncooperative websites. + +const char *FAKE_AGENT_STRING = "FredWeb/7.0 (X11; U; Linux i686; " + "en-US; rv:1.8.19) Flecko/20081031"; + // we use this as our agent type, since some sites won't treat us fairly + // if they think we're robots when we're checking their site health. +//still true? + // for example (ahem!), the usa today websites. + +//////////////////////////////////////////////////////////////////////////// + +class safe_int_array +{ +public: + safe_int_array() : _lock(), _list(0) {} + + void add(int to_add) { +///BASE_LOG(a_sprintf("adding %d to list", to_add)); + auto_synchronizer l(_lock); + _list += to_add; + } + + int length() { + auto_synchronizer l(_lock); + return _list.length(); + } + + basis::int_array make_copy() { + auto_synchronizer l(_lock); + return _list; + } + +private: + basis::mutex _lock; + basis::int_array _list; +}; + +//////////////////////////////////////////////////////////////////////////// + +class marks_checker : public application_shell +{ +public: + marks_checker() + : application_shell(), _check_redirection(false), + _max_threads(MAXIMUM_THREADS), _null_file(filename::null_device(), "w") + {} + + DEFINE_CLASS_NAME("marks_checker"); + virtual int execute(); + int print_instructions(const filename &program_name); + + int test_all_links(); + // goes through the tree of links and tests them all. + + int check_link(const astring &url, astring &error_msg); + // synchronously checks the "url" for health. the return value is zero + // on success or an HTTP error code on failure. + + void write_new_files(); + // writes out the two new files given the info accumulated so far. + +private: + bookmark_tree _categories; // our tree of categories. + safe_int_array _bad_lines; // lines with bad contents. + thread_cabinet _checkers; // threads checking on links. + astring _input_filename; // we'll store our link database name here. + astring _output_filename; // where the list of good links is stored. + astring _bad_link_filename; // garbage dump of bad links. + bool _check_redirection; // true if redirection is disallowed. + int _max_threads; // the most threads we'll allow at once. + byte_filer _null_file; // we'll use this for trashing output data. + + static void handle_OS_signal(int sig_id); + // handles break signals from the user. +}; + +//////////////////////////////////////////////////////////////////////////// + +class checking_thread : public ethread +{ +public: + checking_thread(const link_record &link_info, safe_int_array &bad_lines, + marks_checker &checker) + : ethread(), _bad_lines(bad_lines), _checker(checker), _info(link_info) {} + + void perform_activity(void *formal(ptr)) { + astring message; + int ret = _checker.check_link(_info._url, message); + if (ret != 0) { + astring complaint = a_sprintf("Bad Link at line %d:", _info._uid) + += parser_bits::platform_eol_to_chars(); + const astring spacer(' ', 4); + complaint += spacer + _info._url += parser_bits::platform_eol_to_chars(); + complaint += spacer + _info._description += parser_bits::platform_eol_to_chars(); + complaint += spacer + "error: " += message; + BASE_LOG(complaint); +if ( (_info._uid> 100000) || (_info._uid < 0) ) { +BASE_LOG(a_sprintf("somehow got bogus line number! %d", _info._uid)); +return; +} + _bad_lines.add(_info._uid); // list ours as bad. + } + } + +private: + safe_int_array &_bad_lines; + marks_checker &_checker; + link_record _info; +}; + +//////////////////////////////////////////////////////////////////////////// + +int marks_checker::print_instructions(const filename &program_name) +{ + a_sprintf to_show("%s:\n\ +This program needs three filenames as command line parameters. The -i flag\n\ +is used to specify the input filename. The -o flag specifies the file where\n\ +where the good links will be written. The -b flag specifies the file where\n\ +the bad links are written. The optional flag --no-redirs can be used to\n\ +disallow web-site redirection, which will catch when the site has changed\n\ +its location. Note that redirection is not necessarily an error, but it\n\ +instead may just be a link that needs its URL modified. It is recommended\n\ +that you omit this flag in early runs, in order to only locate definitely\n\ +dead links. Then later checking runs can find any sites that were redirected\n\ +or being routed to a dead link page which doesn't provide an error code.\n\ +The optional flag --threads with a parameter will set the maximum number of\n\ +threads that will simultaneously check on links.\n\ +The input file is expected to be in the HOOPLE link database format.\n\ +The HOOPLE link format is documented here:\n\ + http://hoople.org/guides/link_database/format_manifesto.txt\n\ +", program_name.basename().raw().s(), program_name.basename().raw().s()); + program_wide_logger::get().log(to_show, ALWAYS_PRINT); + return 12; +} + +// this function just eats any data it's handed. +size_t data_sink(void *formal(ptr), size_t size, size_t number, void *formal(stream)) +{ return size * number; } + +int marks_checker::check_link(const astring &url, astring &error_msg) +{ + int to_return = -1; + + CURL *cur = curl_easy_init(); + + curl_easy_setopt(cur, CURLOPT_URL, url.s()); // set the URL itself. + + curl_easy_setopt(cur, CURLOPT_SSL_VERIFYPEER, 0); + // don't verify SSL certificates. + curl_easy_setopt(cur, CURLOPT_MAXFILESIZE, MAXIMUM_READ); + // limit the download size; causes size errors, which we elide to success. + curl_easy_setopt(cur, CURLOPT_NOSIGNAL, 1); + // don't use signals since it interferes with sleep. + curl_easy_setopt(cur, CURLOPT_TIMEOUT, TIME_PER_REQUEST_IN_SEC); + // limit time allowed per operation. + curl_easy_setopt(cur, CURLOPT_AUTOREFERER, true); + // automatically fill in the referer field when redirected. + + curl_easy_setopt(cur, CURLOPT_WRITEDATA, _null_file.file_handle()); + // set the file handle where we want our downloaded data to go. since + // we're just checking the links, this goes right to the trash. + curl_easy_setopt(cur, CURLOPT_WRITEFUNCTION, data_sink); + // set the function which will be given all the downloaded data. + + curl_easy_setopt(cur, CURLOPT_USERAGENT, FAKE_AGENT_STRING); + // fake being a browser here since otherwise we get no respect. + + curl_easy_setopt(cur, CURLOPT_FTPLISTONLY, 1); + // get only a simple list of files, which allows us to hit ftp sites + // properly. if the normal curl mode is used, we get nothing. + + if (_check_redirection) { + // attempting to quash redirects as being valid. + curl_easy_setopt(cur, CURLOPT_FOLLOWLOCATION, 1); // follow redirects. + curl_easy_setopt(cur, CURLOPT_MAXREDIRS, 0); // allow zero redirects. + } + + int tries = 0; + while (tries++ < MAXIMUM_ATTEMPTS) { + + // we do the error message again every time, since it gets shrunk after + // the web page check and is no longer available where it was in memory. + error_msg = astring(' ', CURL_ERROR_SIZE + 5); + curl_easy_setopt(cur, CURLOPT_ERRORBUFFER, error_msg.s()); + + // set the error message buffer so we know what happened. + + // try to lookup the web page we've been given. + to_return = curl_easy_perform(cur); + + error_msg.shrink(); // just use the message without extra spaces. + + // we turn file size errors into non-errors, since we have set a very + // low file size in order to avoid downloading too much. we really just + // want to check links, not download their contents. + if (to_return == CURLE_FILESIZE_EXCEEDED) to_return = 0; + + if (!to_return) { + // supposedly this is a success, but let's check the result code. + long result; + curl_easy_getinfo(cur, CURLINFO_RESPONSE_CODE, &result); + if (result >= 400) { + error_msg = a_sprintf("received http failure code %d", result); + to_return = -1; + } + break; // this was a successful result, a zero outcome from perform. + } + + time_control::sleep_ms(10 * SECOND_ms); // give it a few more seconds... + } + + curl_easy_cleanup(cur); + + return to_return; +} + +int marks_checker::test_all_links() +{ + FUNCDEF("test_all_links"); + // traverse the tree in prefix order. + tree::iterator itty = _categories.access_root().start(tree::prefix); + tree *curr = NIL; + while ( (curr = itty.next()) ) { + inner_mark_tree *nod = dynamic_cast(curr); + if (!nod) + non_continuable_error(static_class_name(), func, "failed to cast a tree node"); + // iterate on all the links at this node to check them. + for (int i = 0; i < nod->_links.elements(); i++) { + link_record *lin = nod->_links.borrow(i); + if (!lin->_url) continue; // not a link. + + while (_checkers.threads() > _max_threads) { + time_control::sleep_ms(PAUSEY_SNOOZE); + _checkers.clean_debris(); + } + + checking_thread *new_thread = new checking_thread(*lin, _bad_lines, + *this); + unique_int id = _checkers.add_thread(new_thread, true, NIL); + } + } + +BASE_LOG("... finished iterating on tree."); + + // now wait until all the threads are finished. + while (_checkers.threads()) { + time_control::sleep_ms(PAUSEY_SNOOZE); + _checkers.clean_debris(); + } + +BASE_LOG("... finished waiting for all threads."); + + return 0; +} + +void marks_checker::write_new_files() +{ + byte_filer input_file(_input_filename, "r"); + byte_filer output_file(_output_filename, "w"); + byte_filer badness_file(_bad_link_filename, "w"); + + basis::int_array badness = _bad_lines.make_copy(); + shell_sort(badness.access(), badness.length()); + + BASE_LOG("bad links are on lines:"); + astring bad_list; + for (int i = 0; i < badness.length(); i++) { + bad_list += a_sprintf("%d, ", badness[i]); + } + BASE_LOG(bad_list); + + astring buffer; + int curr_line = 0; + while (!input_file.eof()) { + curr_line++; + while (badness.length() && (badness[0] < curr_line) ) { + BASE_LOG(a_sprintf("whacking too low line number: %d", badness[0])); + badness.zap(0, 0); + } + input_file.getline(buffer, 2048); +//make that a constant. + if (badness.length() && (badness[0] == curr_line)) { + // we seem to have found a bad line. + badness_file.write(buffer); + badness.zap(0, 0); // remove the current line number. + } else { + // this is a healthy line. + output_file.write(buffer); + } + + } + input_file.close(); + output_file.close(); + badness_file.close(); +} + +marks_checker *main_program = NIL; + +void marks_checker::handle_OS_signal(int formal(sig_id)) +{ + signal(SIGINT, SIG_IGN); // turn off that signal for now. + BASE_LOG("caught break signal... now writing files."); + if (main_program) main_program->write_new_files(); + BASE_LOG("exiting after handling break."); + main_program = NIL; + exit(0); +} + +int marks_checker::execute() +{ + FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + + main_program = this; // used by our signal handler. + + command_line cmds(_global_argc, _global_argv); // process the command line parameters. + if (!cmds.get_value('i', _input_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('o', _output_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('b', _bad_link_filename, false)) + return print_instructions(cmds.program_name()); + + astring temp; + + // optional flag for checking website redirection. + if (cmds.get_value("no-redirs", temp, false)) { + BASE_LOG("Enabling redirection checking: redirected web sites are reported as bad."); + _check_redirection = true; + } + // optional flag for number of threads. + astring threads; + if (cmds.get_value("threads", threads, false)) { + _max_threads = threads.convert(0); + BASE_LOG(a_sprintf("Maximum threads allowed=%d", _max_threads)); + } + + BASE_LOG(astring("input file: ") + _input_filename); + BASE_LOG(astring("output file: ") + _output_filename); + BASE_LOG(astring("bad link file: ") + _bad_link_filename); + +//hmmm: check if output file already exists. +//hmmm: check if bad file already exists. + +LOG("before reading input..."); + + int ret = _categories.read_csv_file(_input_filename); + if (ret) return ret; // failure during read means we can't do much. + +LOG("after reading input..."); + + signal(SIGINT, handle_OS_signal); + // hook the break signal so we can still do part of the job if they + // interrupt us. + + curl_global_init(CURL_GLOBAL_ALL); // crank up the cURL engine. + + ret = test_all_links(); + + write_new_files(); + main_program = NIL; + + curl_global_cleanup(); // shut down cURL engine again. + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(marks_checker, ) + diff --git a/nucleus/applications/bookmark_tools/marks_maker.cpp b/nucleus/applications/bookmark_tools/marks_maker.cpp new file mode 100644 index 00000000..8e2cbef5 --- /dev/null +++ b/nucleus/applications/bookmark_tools/marks_maker.cpp @@ -0,0 +1,416 @@ +////////////// +// Name : marks_maker +// Author : Chris Koeritz +////////////// +// Copyright (c) 2005-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace nodes; +using namespace structures; +using namespace textual; +using namespace timely; + +//#define DEBUG_MARKS + // uncomment to have more debugging noise. + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +const int MAX_FILE_SIZE = 4 * MEGABYTE; + // the largest file we'll read. + +const int SPACING_CHUNK = 4; + // number of spaces per indentation level. + +const int MAX_URL_DISPLAYED = 58; +const int MAX_DESCRIP_DISPLAYED = 72; + +//////////////////////////////////////////////////////////////////////////// + +class marks_maker : public application_shell +{ +public: + marks_maker(); + + enum output_style { + ST_HUMAN_READABLE, + ST_MOZILLA_MARKS, +// ST_JAVASCRIPT_BASED... separate implementation currently. + }; + + int write_marks_page(const astring &output_filename, + const astring &template_filename, output_style way); + // given a tree of links, this writes out a web page to "output_filename" + // using a template file "template_filename". + + DEFINE_CLASS_NAME("marks_maker"); + int print_instructions(const filename &program_name); + virtual int execute(); + +private: + bookmark_tree c_categories; // our tree of categories. + int c_current_depth; // current indentation depth in list. + output_style c_style; // style of marks to write, set after construction. + + void increase_nesting(astring &output); + // adds a new level of nesting to the text. + + void decrease_nesting(astring &output); + // drops out of a level of nesting in the text. + + astring recurse_on_node(inner_mark_tree *nod); + // the main recursive method that spiders down the tree. it is important that it builds + // the string through composition rather than being given a string reference, since it + // expands all sub-trees as it goes. + + void inject_javascript_function(astring &output); + // replaces a special phrase in the template file with our javascript-based link opener. + + void write_category_start(const astring &name, int node_depth, astring &output); + // outputs the text for categories and adjusts the indentation level. + + void write_category_end(int depth, astring &output); + // closes a category appropriately for the nesting depth. + + void write_link(inner_mark_tree *node, const link_record &linko, + astring &output, int depth); + // outputs the text for web links. +}; + +//////////////////////////////////////////////////////////////////////////// + +marks_maker::marks_maker() +: application_shell(), + c_current_depth(0), + c_style(ST_HUMAN_READABLE) +{} + +int marks_maker::print_instructions(const filename &program_name) +{ + a_sprintf to_show("%s:\n\ +This program needs three filenames as command line parameters. The -i flag\n\ +is used to specify the input filename, the -t flag specifies a template web\n\ +page which is used as the wrapper around the links, and the -o flag specifies\n\ +the web page to be created. The input file is expected to be in the HOOPLE\n\ +link database format. The output file will be created from the template file\n\ +by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\ +formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\ +will be replaced with the date when the output file is regenerated. A final\n\ +tag of $INSERT_JAVASCRIPT_HERE is replaced with a link opening function.\n\ +Note that an optional -s flag can specify a value of \"human\" readable\n\ +or \"mozilla\" bookmarks style to specify the style of the output file\n\ +generated.\n\ +The HOOPLE link format is documented here:\n\ + http://hoople.org/guides/link_database/format_manifesto.txt\n\ +", program_name.basename().raw().s(), program_name.basename().raw().s()); + program_wide_logger::get().log(to_show, ALWAYS_PRINT); + return 12; +} + +void marks_maker::increase_nesting(astring &output) +{ + FUNCDEF("increase_nesting"); + int spaces = SPACING_CHUNK * c_current_depth; + c_current_depth++; +#ifdef DEBUG_MARKS + LOG(a_sprintf("++increased depth to %d...", c_current_depth)); +#endif + output += string_manipulation::indentation(spaces); + output += "

    "; + output += parser_bits::platform_eol_to_chars(); +} + +void marks_maker::decrease_nesting(astring &output) +{ + FUNCDEF("decrease_nesting"); + c_current_depth--; +#ifdef DEBUG_MARKS + LOG(a_sprintf("--decreased depth to %d...", c_current_depth)); +#endif + int spaces = SPACING_CHUNK * c_current_depth; + output += string_manipulation::indentation(spaces); + output += "

    "; + output += parser_bits::platform_eol_to_chars(); +} + +void marks_maker::write_category_start(const astring &name, int node_depth, astring &output) +{ + FUNCDEF("write_category_start"); + + // calculate proper heading number. + int heading_num = node_depth + 1; + astring heading = a_sprintf("%d", heading_num); + // force a weird requirement for mozilla bookmarks, all headings must be set at 3. + if (c_style == ST_MOZILLA_MARKS) heading = "3"; + +#ifdef DEBUG_MARKS + LOG(astring("header [") + name + "] level " + a_sprintf("%d", node_depth)); +#endif + + // output our heading. + output += string_manipulation::indentation(c_current_depth * SPACING_CHUNK); + output += "

    MAX_URL_DISPLAYED) { + chomped_url.zap(MAX_URL_DISPLAYED / 2, + chomped_url.length() - MAX_URL_DISPLAYED / 2 - 1); + chomped_url.insert(MAX_URL_DISPLAYED / 2, "..."); + } + } + + astring description = linko._description; + if (c_style != ST_MOZILLA_MARKS) { + // this is chopping the tail off, which seems reasonable for a very long description. + if (description.length() > MAX_DESCRIP_DISPLAYED) { + description.zap(MAX_DESCRIP_DISPLAYED - 1, description.end()); + description += "..."; + } + } + + // new output format, totally clean and simple. description is there + // in readable manner, and it's also a link. plus, this takes up a fraction + // of the space the old way used. + astring indentulus = string_manipulation::indentation(c_current_depth * SPACING_CHUNK); + output += indentulus; + output += "
  • "; + output += ""; + output += description; + output += ""; + + if (c_style != ST_MOZILLA_MARKS) { + output += "   "; + output += ""; + output += "[launch]"; + output += ""; + } + + output += "
  • "; + output += "
    "; + output += parser_bits::platform_eol_to_chars(); +} + +astring marks_maker::recurse_on_node(inner_mark_tree *nod) +{ + FUNCDEF("recurse_on_node"); + astring to_return; + + // print out the category on this node. + write_category_start(nod->name(), nod->depth(), to_return); + + // print the link for all of the ones stored at this node. + for (int i = 0; i < nod->_links.elements(); i++) { + link_record *lin = nod->_links.borrow(i); + write_link(nod, *lin, to_return, nod->depth()); + } + + // zoom down into sub-categories. + for (int i = 0; i < nod->branches(); i++) { + to_return += recurse_on_node((inner_mark_tree *)nod->branch(i)); + } + + // finish this category. + write_category_end(nod->depth(), to_return); + + return to_return; +} + +void marks_maker::inject_javascript_function(astring &output) +{ + FUNCDEF("inject_javascript_function"); + astring scrip = "\n\ +\n\ +\n"; + + bool found_it = output.replace("$INSERT_JAVASCRIPT_HERE", scrip); + if (!found_it) + non_continuable_error(class_name(), func, "the template file is missing " + "the insertion point for '$INSERT_JAVASCRIPT_HERE'"); +} + +int marks_maker::write_marks_page(const astring &output_filename, + const astring &template_filename, output_style style) +{ + FUNCDEF("write_marks_page"); + c_style = style; // set the overall output style here. + astring long_string; + // this is our accumulator of links. it is the semi-final result that will + // be injected into the template file. + + // generate the meaty portion of the bookmarks. + increase_nesting(long_string); + inner_mark_tree *top = (inner_mark_tree *)&c_categories.access_root(); + long_string += recurse_on_node(top); + decrease_nesting(long_string); + + byte_filer template_file(template_filename, "r"); + astring full_template; + if (!template_file.good()) + non_continuable_error(class_name(), func, "the template file could not be opened"); + template_file.read(full_template, MAX_FILE_SIZE); + template_file.close(); + + // spice up the boring template with a nice link opening function. + inject_javascript_function(full_template); + + // replace the tag with the long string we created. + bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string); + if (!found_it) + non_continuable_error(class_name(), func, "the template file is missing " + "the insertion point for '$INSERT_LINKS_HERE'"); + + full_template.replace("$TODAYS_DATE", time_stamp::notarize(true)); + + filename outname(output_filename); + byte_filer output_file(output_filename, "w"); + if (!output_file.good()) + non_continuable_error(class_name(), func, "the output file could not be opened"); + // write the newly generated web page out now. + output_file.write(full_template); + output_file.close(); + +#ifdef DEBUG_MARKS + // show the tree. + BASE_LOG(astring()); + BASE_LOG(astring("the tree, sir...")); + BASE_LOG(astring()); + BASE_LOG(c_categories.access_root().text_form()); +#endif + + BASE_LOG(a_sprintf("wrote %d links in %d categories.", + c_categories.link_count(), c_categories.category_count())); + BASE_LOG(astring("")); + + return 0; +} + +int marks_maker::execute() +{ + FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + + command_line cmds(_global_argc, _global_argv); // process the command line parameters. + astring input_filename; // we'll store our link database name here. + astring output_filename; // where the web page we're creating goes. + astring template_filename; // the wrapper html code that we'll stuff. + astring style_used; // type of output file style to create. + if (!cmds.get_value('i', input_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('o', output_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('t', template_filename, false)) + return print_instructions(cmds.program_name()); + cmds.get_value('s', style_used, false); + if (!style_used) style_used = "human"; + + BASE_LOG(astring("input file: ") + input_filename); + BASE_LOG(astring("output file: ") + output_filename); + BASE_LOG(astring("template file: ") + template_filename); + BASE_LOG(astring("style: ") + style_used); + + filename outname(output_filename); + if (outname.exists()) { + non_continuable_error(class_name(), func, astring("the output file ") + + output_filename + " already exists. It would be over-written if " + "we continued."); + } + + output_style styley = ST_HUMAN_READABLE; + if (style_used == astring("mozilla")) styley = ST_MOZILLA_MARKS; + + int ret = c_categories.read_csv_file(input_filename); + if (ret) return ret; + + ret = write_marks_page(output_filename, template_filename, styley); + if (ret) return ret; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(marks_maker, ) + diff --git a/nucleus/applications/bookmark_tools/marks_sorter.cpp b/nucleus/applications/bookmark_tools/marks_sorter.cpp new file mode 100644 index 00000000..b793d373 --- /dev/null +++ b/nucleus/applications/bookmark_tools/marks_sorter.cpp @@ -0,0 +1,232 @@ +/*****************************************************************************\ +* * +* Name : marks_sorter * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Processes a link database in HOOPLE format and generates a new database * +* that is sorted and always uses category nicknames where defined. * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bookmark_tree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace nodes; +using namespace structures; +using namespace textual; + +//#define DEBUG_MARKS + // uncomment to have more debugging noise. + +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \ + a_sprintf("line %d: ", _categories._line_number) + s) + +const int MAX_FILE_SIZE = 4 * MEGABYTE; + // the largest file we'll read. + +//////////////////////////////////////////////////////////////////////////// + +class marks_sorter : public application_shell +{ +public: + marks_sorter() + : application_shell(), _loader_count(0), _link_spool(0) {} + DEFINE_CLASS_NAME("marks_sorter"); + virtual int execute(); + int print_instructions(const filename &program_name); + + int write_new_marks(const astring &output_filename); + // given a tree of links, this writes out a new sorted file to the + // "output_filename". + +private: + bookmark_tree _categories; // our tree of categories. + int _loader_count; // count of the loader functions. + int _link_spool; // count of which link we're writing. +}; + +//////////////////////////////////////////////////////////////////////////// + +int marks_sorter::print_instructions(const filename &program_name) +{ + a_sprintf to_show("%s:\n\ +This program needs two filenames as command-line parameters. The -i flag\n\ +is used to specify the input filename, which is expected to be in the HOOPLE\n\ +link database format. The -o flag specifies the new bookmarks file to be\n\ +created, which will also be in the HOOPLE link format.\n\ +The HOOPLE link format is documented here:\n\ + http://hoople.org/guides/link_database/format_manifesto.txt\n\ +", program_name.basename().raw().s(), program_name.basename().raw().s()); + program_wide_logger::get().log(to_show, ALWAYS_PRINT); + return 12; +} + +int marks_sorter::execute() +{ + FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + + command_line cmds(_global_argc, _global_argv); // process the command line parameters. + astring input_filename; // we'll store our link database name here. + astring output_filename; // where the web page we're creating goes. + if (!cmds.get_value('i', input_filename, false)) + return print_instructions(cmds.program_name()); + if (!cmds.get_value('o', output_filename, false)) + return print_instructions(cmds.program_name()); + + BASE_LOG(astring("input file: ") + input_filename); + BASE_LOG(astring("output file: ") + output_filename); + + filename outname(output_filename); + if (outname.exists()) { + non_continuable_error(class_name(), func, astring("the output file ") + + output_filename + " already exists. It would be over-written if " + "we continued."); + } + + int ret = _categories.read_csv_file(input_filename); + if (ret) return ret; + + ret = write_new_marks(output_filename); + if (ret) return ret; + + return 0; +} + +int marks_sorter::write_new_marks(const astring &output_filename) +{ + FUNCDEF("write_new_marks"); + // open the output file for streaming out the new marks file. + filename outname(output_filename); + byte_filer output_file(output_filename, "w"); + if (!output_file.good()) + non_continuable_error(class_name(), func, "the output file could not be opened"); + + bool just_had_return = false; // did we just see a carriage return? + bool first_line = true; // is this the first line to be emitted? + + // traverse the tree in prefix order. + tree::iterator itty = _categories.access_root().start(tree::prefix); + tree *curr = NIL; // the current node. + + while ( (curr = itty.next()) ) { + inner_mark_tree *nod = (inner_mark_tree *)curr; + // set up a category printout for this node. + string_array cat_list; + cat_list += "C"; + cat_list += nod->name(); + inner_mark_tree *pare = (inner_mark_tree *)nod->parent(); + if (pare) { + astring name_split, nick_split; + _categories.break_name(pare->name(), name_split, nick_split); + if (!nick_split) cat_list += name_split; + else cat_list += nick_split; + } else { + cat_list += ""; + } + + // create a text line to send to the output file. + astring tmp; + list_parsing::create_csv_line(cat_list, tmp); + tmp += "\n"; + if (!just_had_return && !first_line) { + // generate a blank line before the category name. + output_file.write(parser_bits::platform_eol_to_chars()); + } + + // reset the flags after we've checked them. + just_had_return = false; + first_line = false; + + output_file.write(tmp); + // write the actual category definition. + + // print the links for all of the ones stored at this node. + for (int i = 0; i < nod->_links.elements(); i++) { + link_record *lin = nod->_links.borrow(i); + if (!lin->_url) { + // just a comment. + astring descrip = lin->_description; + if (descrip.contains("http:")) { + // we'll clean the html formatting out that we added earlier. + int indy = descrip.find('"'); + if (non_negative(indy)) { + descrip.zap(0, indy); + indy = descrip.find('"'); + if (non_negative(indy)) descrip.zap(indy, descrip.end()); + } + descrip = astring(" ") + descrip; + // add a little spacing. + } + if (descrip.t()) { + output_file.write(astring("#") + descrip + "\n"); + just_had_return = false; + } else { + // this line's totally blank, so we'll generate a blank line. + // we don't want to put in more than one blank though, so we check + // whether we did this recently. + if (!just_had_return) { + output_file.write(parser_bits::platform_eol_to_chars()); + just_had_return = true; // set our flag for a carriage return. + } + } + } else { + // should be a real link. + string_array lnks; + lnks += "L"; + lnks += lin->_description; + // use just the nickname for the parent, if there is a nick. + astring name_split; + astring nick_split; + _categories.break_name(nod->name(), name_split, nick_split); + if (!nick_split) lnks += nod->name(); + else lnks += nick_split; + lnks += lin->_url; + list_parsing::create_csv_line(lnks, tmp); + tmp += "\n"; + output_file.write(tmp); + just_had_return = false; + } + } + } + + output_file.close(); + + BASE_LOG(a_sprintf("wrote %d links in %d categories.", + _categories.link_count(), _categories.category_count())); + BASE_LOG(astring()); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(marks_sorter, ) + diff --git a/nucleus/applications/bookmark_tools/version.ini b/nucleus/applications/bookmark_tools/version.ini new file mode 100644 index 00000000..ef1e3c14 --- /dev/null +++ b/nucleus/applications/bookmark_tools/version.ini @@ -0,0 +1,6 @@ +[version] +description = Bookmark Utilities +root = bookmark +name = Bookmark Apps +extension = exe + diff --git a/nucleus/applications/bundler/bundle_creator.cpp b/nucleus/applications/bundler/bundle_creator.cpp new file mode 100644 index 00000000..5a6722a9 --- /dev/null +++ b/nucleus/applications/bundler/bundle_creator.cpp @@ -0,0 +1,1007 @@ + +//hmmm: anything related to _stub_size should be kept, but that is where +// we need a redundant search mechanism that can't be fooled so easily +// by modifying exe; make a pattern that will be found and is the first +// place to start looking for manifest. + +/*****************************************************************************\ +* * +* Name : bundle_creator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "common_bundle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef __WIN32__ + #include +#endif + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace filesystem; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; + +const int CHUNKING_SIZE = 256 * KILOBYTE; + // we'll read this big a chunk from a source file at a time. + +const astring SUBVERSION_FOLDER = ".svn"; + // we don't want to include this in a bundle. + +#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) + +//#define DEBUG_BUNDLER + // uncomment for noisy debugging version. + +// returns the "retval" and mentions that this is a failure at "where". +#define FAIL_RETURN(retval, where) { \ + LOG(astring("failure in ") + where + a_sprintf(", exit=%d", retval)); \ + return retval; \ +} + +//////////////////////////////////////////////////////////////////////////// + +bool true_value(const astring &value) +{ return (!value.equal_to("0")) && (!value.equal_to("false")); } + +//////////////////////////////////////////////////////////////////////////// + +// this structure overrides the manifest_chunk by providing a source string. + +struct bundled_chunk : manifest_chunk +{ + astring _source; //!< where the file comes from on the source system. + virtual ~bundled_chunk() {} +}; + +//////////////////////////////////////////////////////////////////////////// + +// main bundler class. + +class bundle_creator : public application_shell +{ +public: + bundle_creator() + : application_shell(), + _app_name(filename(_global_argv[0]).basename()), + _bundle(NIL), _stub_size(0), _keyword() {} + + virtual ~bundle_creator() { + WHACK(_bundle); + } + + DEFINE_CLASS_NAME("bundle_creator"); + virtual int execute(); + int print_instructions(); + + astring determine_stub_file_and_validate(); + //!< returns the stub file location if it could be successfully located. + + int open_output_file(); + //!< prepares the output file to be written into. + /*!< non-zero return indicates an error. */ + + int read_manifest(); + //!< reads manifest definition specifying files in the bundle. + /*!< creates the list of bundle pieces. */ + + int write_stub_and_toc(); + //!< stuffs the unpacker stub into output file and table of contents. + + int bundle_sources(); + //!< reads all of the input files and dumps them into the bundle. + + int finalize_file(); + //!< puts finishing touches on the output file and closes it. + + int write_offset(); + //!< writes the offset position into the output file. + /*!< this happens at the specially marked location (muftiloc). */ + + int patch_recursive_target(const astring &source, const astring &target, + int manifest_index); + //!< processes the recursive target specified in "curr". + /*!< the manifest_index tells the function where the external caller + is currently working on the manifest. new items will appear just after + that index. */ + + int recurse_into_dir(const astring &source, const astring &target, + int manifest_index); + //!< adds all files from "source" to our list, recurses on dirs. + + int patch_wildcard_target(const astring &source, const astring &target, + int manifest_index); + //!< processes the wildcard bearing target specified in "curr". + /*!< any new source items will get dropped on the end of the manifest. */ + + int add_files_here(directory &dirndl, const astring &source, + const astring &target, int manifest_index); + //!< takes all the files found in "source" and adds them to manifest. + + bool get_file_size(const astring &file, un_int &size, byte_array ×tamp); + //!< returns the file "size" and "timestamp" found for "file". + +private: + astring _app_name; //!< application name for this program. + astring _output_file; //!< what bundle file to create. + astring _manifest_file; //!< the manifest of what's included in bundle. + array _manifest_list; //!< the parsed list of contents. + byte_filer *_bundle; //!< points at the bundled output file. + int _stub_size; //!< where the TOC will be located. + astring _keyword; // set if we were given a keyword on cmd line. +}; + +//////////////////////////////////////////////////////////////////////////// + +int bundle_creator::print_instructions() +{ + BASE_LOG(a_sprintf("\ +%s: This program needs two parameters on the command line.\n\ +The -o flag must point at the bundled output file to create. The -m flag\n\ +must point at a valid manifest file that defines what will be packed into\n\ +the output file. See the example manifest in the bundler example\n\ +(in setup_src/bundle_example) for more information on the required file\n\ +format.\n\ +", _app_name.s())); + return 4; +} + +int bundle_creator::execute() +{ + FUNCDEF("execute"); + + BASE_LOG(astring("starting file bundling at ") + time_stamp::notarize(false)); + + command_line cmds(_global_argc, _global_argv); + astring temp; + if (cmds.get_value('?', temp)) return print_instructions(); + if (cmds.get_value("?", temp)) return print_instructions(); + if (!cmds.get_value('o', _output_file)) return print_instructions(); + if (!cmds.get_value('m', _manifest_file)) return print_instructions(); + + if (filename(_output_file).exists()) { + BASE_LOG(a_sprintf("\ +%s: The output file already exists. Please move it out of\n\ +the way; this program will not overwrite existing files.\n", +_app_name.s())); + return 3; + } + + if (!filename(_manifest_file).exists()) { + BASE_LOG(a_sprintf("\ +%s: The manifest file does not exist. This program cannot do anything\n\ +without a valid packing manifest.\n", _app_name.s())); + return 2; + } + + // test this early on so we don't waste time uselessly. + astring stub_file_okay = determine_stub_file_and_validate(); + if (!stub_file_okay) { + BASE_LOG(a_sprintf("\ +%s: The unpacking stub file does not exist (check binaries folder).\n\ +Abandoning bundling process.\n", _app_name.s())); + return 4; + } + + // make sure we snag any keyword that was passed on the command line. + cmds.get_value("keyword", _keyword); + + // first step is to provide some built-in variables that can be used to + // make the manifests less platform specific. this doesn't really help + // if you bundle it on linux and try to run it on windows. but either + // platform's resources can easily be made into a bundle with the same + // packing manifest. +#ifndef __WIN32__ + environment::set("EXE_END", ""); // executable file ending. + environment::set("DLL_START", "lib"); // dll file prefix. + environment::set("DLL_END", ".so"); // dll file ending. +#else + environment::set("EXE_END", ".exe"); + environment::set("DLL_START", ""); + environment::set("DLL_END", ".dll"); +#endif + + int ret = 0; + if ( (ret = read_manifest()) ) FAIL_RETURN(ret, "reading manifest"); + // read manifest to build list of what's what. + if ( (ret = open_output_file()) ) FAIL_RETURN(ret, "opening output file"); + // open up our output file for the bundled chunks. + if ( (ret = write_stub_and_toc()) ) FAIL_RETURN(ret, "writing stub and TOC"); + // writes the stub unpacker application and the table of contents to the + // output file. + if ( (ret = bundle_sources()) ) FAIL_RETURN(ret, "bundling source files"); + // stuff all the source files into the output bundle. + if ( (ret = finalize_file()) ) FAIL_RETURN(ret, "finalizing file"); + // finishes with the file and closes it up. + if ( (ret = write_offset()) ) FAIL_RETURN(ret, "writing offset"); + // stores the offset of the TOC into the output file in a special location + // that is delineated by a known keyword (muftiloc) and which should only + // exist in the file in one location. + + return 0; +} + +int bundle_creator::open_output_file() +{ + FUNCDEF("open_output_file"); + _bundle = new byte_filer(_output_file, "wb"); + if (!_bundle->good()) { + LOG(astring("failed to open the output file: ") + _output_file); + return 65; + } + return 0; +} + +bool bundle_creator::get_file_size(const astring &infile, un_int &size, + byte_array &time_stamp) +{ + FUNCDEF("get_file_size"); + time_stamp.reset(); + // access the source file to get its size. + byte_filer source_file(infile, "rb"); + if (!source_file.good()) { + LOG(astring("could not access the file for size check: ") + infile); + return false; + } + size = int(source_file.length()); + file_time tim(infile); + tim.pack(time_stamp); + return true; +} + +int bundle_creator::add_files_here(directory &dirndl, const astring &source, + const astring &target, int manifest_index) +{ + FUNCDEF("add_files_here"); + for (int i = 0; i < dirndl.files().length(); i++) { + astring curry = dirndl.files()[i]; + // skip .svn folders and contents. + if (curry.contains(SUBVERSION_FOLDER)) continue; +//hmmm: this could be a much nicer generalized file exclusion list. + +//LOG(astring("file is: ") + curry); + bundled_chunk new_guy; + new_guy._source = source + "/" + curry; // the original full path to it. + new_guy._payload = target + "/" + curry; + new_guy._keywords = _manifest_list[manifest_index]._keywords; + // copy the flags from the parent, so we don't forget options. + new_guy._flags = _manifest_list[manifest_index]._flags; + // remove some flags that make no sense for the new guy. + new_guy._flags &= ~RECURSIVE_SRC; + +//LOG(a_sprintf("adding: source=%s targ=%s", new_guy._source.s(), new_guy._payload.s())); + bool okaysize = get_file_size(new_guy._source, new_guy._size, new_guy.c_filetime); + if (!okaysize || (new_guy._size < 0) ) { + LOG(astring("failed to get file size for ") + new_guy._source); + return 75; + } + + _manifest_list.insert(manifest_index + 1, 1); + _manifest_list[manifest_index + 1] = new_guy; + } + return 0; +} + +int bundle_creator::recurse_into_dir(const astring &source, + const astring &target, int manifest_index) +{ +// FUNCDEF("recurse_into_dir"); +//LOG(astring("src=") + source + " dest=" + target); + + // we won't include the subversion folder. + if (source.contains(SUBVERSION_FOLDER)) return 0; + + string_array dirs; // culled from the directory listing. + { + // don't pay for the directory object on the recursive invocation stack; + // just have what we need on the stack (the directory list). + directory dirndl(source); +//check dir for goodness! + int ret = add_files_here(dirndl, source, target, manifest_index); + // add in just the files that were found. + if (ret != 0) { + // this is a failure, but the function complains about it already. + return 75; + } + dirs = dirndl.directories(); + } + +//LOG("now scanning directories..."); + + // now scan across the directories we found. + for (int i = 0; i < dirs.length(); i++) { + astring s = dirs[i]; +//LOG(astring("curr dir is ") + s); + int ret = recurse_into_dir(source + "/" + s, target + "/" + + s, manifest_index); + if (ret != 0) return ret; // bail out. + } + + return 0; +} + +int bundle_creator::patch_recursive_target(const astring &source, + const astring &target, int manifest_index) +{ +// FUNCDEF("patch_recursive_target"); +//LOG(astring("patch recurs src=") + source + " targ=" + target); + return recurse_into_dir(source, target, manifest_index); +} + +int bundle_creator::patch_wildcard_target(const astring &source, + const astring &target, int manifest_index) +{ +// FUNCDEF("patch_wildcard_target"); + // find the last slash. the rest is our wildcard component. + int src_end = source.end(); + int slash_indy = source.find('/', src_end, true); + astring real_source = source.substring(0, slash_indy - 1); + astring wild_pat = source.substring(slash_indy + 1, src_end); +//BASE_LOG(astring("got src=") + real_source + " wildpat=" + wild_pat); + + directory dirndl(real_source, wild_pat.s()); +//check dir for goodness! + int ret = add_files_here(dirndl, real_source, target, manifest_index); + if (ret != 0) { + // this is a failure, but the function complains about it already. + return 75; + } + + return 0; +} + +int bundle_creator::read_manifest() +{ + FUNCDEF("read_manifest"); + ini_configurator ini(_manifest_file, configurator::RETURN_ONLY); + string_table toc; + bool worked = ini.get_section("toc", toc); + if (!worked) { + LOG(astring("failed to read TOC section in manifest:\n") + _manifest_file + + "\ndoes that file exist?"); + return 65; + } + +//hmmm: make a class member. + file_logger noisy_logfile(application_configuration::make_logfile_name + ("bundle_creator_activity.log")); + noisy_logfile.log(astring('-', 76)); + noisy_logfile.log(astring("Bundling starts at ") + time_stamp::notarize(false)); + + // add enough items in the list for our number of sections. + _manifest_list.insert(0, toc.symbols()); + astring value; // temporary string used below. + int final_return = 0; // if non-zero, an error occurred. + +#define BAIL(retval) \ + final_return = retval; \ + toc.zap_index(i); \ + _manifest_list.zap(i, i); \ + i--; \ + continue + + for (int i = 0; i < toc.symbols(); i++) { + // read all the info in this section and store it into our list. + astring section_name = toc.name(i); + section_name.strip_spaces(astring::FROM_FRONT); + if (section_name[0] == '#') { +//hmmm: this looks a bit familiar from bail macro above. abstract out? + toc.zap_index(i); + _manifest_list.zap(i, i); + i--; + continue; // skip comments. + } + + // check for any keywords on the section. these are still needed for + // variables, which otherwise would skip the rest of the field checks. + if (ini.get(section_name, "keyword", value)) { +///LOG(astring("into keyword processing--value held is ") + value); + string_array keys; + bool worked = list_parsing::parse_csv_line(value, keys); + if (!worked) { + LOG(astring("failed to parse keywords for section ") + + section_name + " in " + _manifest_file); + BAIL(82); + } +///LOG(astring("parsed list is ") + keys.text_form()); + _manifest_list[i]._keywords = keys; + astring dumped; + list_parsing::create_csv_line(_manifest_list[i]._keywords, dumped); + noisy_logfile.log(section_name + " keywords: " + dumped); + } + + if (ini.get(section_name, "variable", value)) { + // this is a variable assignment. it is the only thing we care about + // for this section, so the rest is ignored. + variable_tokenizer zohre; + zohre.parse(value); + if (zohre.symbols() < 1) { + LOG(astring("failed to parse a variable statement from ") + value); + BAIL(37); + } + _manifest_list[i]._flags = SET_VARIABLE; // not orred, just this. + // set the two parts of our variable. + _manifest_list[i]._payload = zohre.table().name(0); + _manifest_list[i]._parms = zohre.table()[0]; + BASE_LOG(astring("will set ") + _manifest_list[i]._payload + " = " + + _manifest_list[i]._parms); + astring new_value = parser_bits::substitute_env_vars(_manifest_list[i]._parms); + environment::set(_manifest_list[i]._payload, new_value); + +#ifdef DEBUG_BUNDLER + BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " should have value=" + new_value); + BASE_LOG(astring("** variable ") + _manifest_list[i]._payload + " now does have value=" + environment::get(_manifest_list[i]._payload)); +#endif + + continue; + } else if (ini.get(section_name, "assert_defined", value)) { + // they are just asking for a variable test, to see if a variable + // that the installer needs is actually defined at unpacking time. + _manifest_list[i]._payload = value; + _manifest_list[i]._flags = TEST_VARIABLE_DEFINED; + BASE_LOG(astring("will test ") + _manifest_list[i]._payload + " is " + + "defined at unpacking time."); + continue; + } + + if (!ini.get(section_name, "source", _manifest_list[i]._source)) { + // check whether they told us not to pack and it's executable. + bool okay_to_omit_source = false; + astring value2; + if (ini.get(section_name, "no_pack", value) + && ini.get(section_name, "exec_target", value2) ) { + if (true_value(value) && true_value(value2)) { + // this type of section doesn't need source declared. + okay_to_omit_source = true; + } + } + if (!okay_to_omit_source) { + LOG(astring("failed to read the source entry for section ") + + section_name + " in " + _manifest_file); + BAIL(67); + } + } + // fix meshugener backslashes so we can count on the slash direction. + _manifest_list[i]._source.replace_all('\\', '/'); + + if (!ini.get(section_name, "target", _manifest_list[i]._payload)) { + // check whether they told us not to pack and it's executable. + bool okay_to_omit_target = false; + astring value2; + if (ini.get(section_name, "no_pack", value) + && ini.get(section_name, "exec_source", value2) ) { + if (true_value(value) && true_value(value2)) { + // this type of section doesn't need target declared. + okay_to_omit_target = true; + } + } + if (!okay_to_omit_target) { + LOG(astring("failed to read the target entry for section ") + + section_name + " in " + _manifest_file); + BAIL(68); + } + } + // fix backslashes in target also. + _manifest_list[i]._payload.replace_all('\\', '/'); + + // capture any parameters they have specified for exec or other options. + if (ini.get(section_name, "parms", value)) { + _manifest_list[i]._parms = value; +#ifdef DEBUG_BUNDLER + BASE_LOG(astring("got parms for ") + section_name + " as: " + value); +#endif + if (value[0] != '"') { + // repair the string if we're running on windows. + _manifest_list[i]._parms = astring("\"") + value + "\""; + } + noisy_logfile.log(section_name + " parms: " + _manifest_list[i]._parms); + } + + // check for the ignore errors flag. + if (ini.get(section_name, "error_okay", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= IGNORE_ERRORS; + } + + // see if they are saying not to overwrite the target file. + if (ini.get(section_name, "no_replace", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= NO_OVERWRITE; + } + + // test whether they are saying not to complain about a failure with + // our normal pop-up dialog (on winders). + if (ini.get(section_name, "quiet", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= QUIET_FAILURE; + } + + // did they want a backup of the original to be made, instead of + // just overwriting the file? + if (ini.get(section_name, "make_backup", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= MAKE_BACKUP_FILE; + } + + // look for our recursion flag. + if (ini.get(section_name, "recurse", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= RECURSIVE_SRC; + } else { + // the options here are only appropriate when the target is NOT set to + // be recursive. + + if (ini.get(section_name, "no_pack", value)) { + // allow either side to not be required if this is an executable. + if (true_value(value)) + _manifest_list[i]._flags |= OMIT_PACKING; + } + + // check if they have specified a source side executable. + if (ini.get(section_name, "exec_source", value)) { + if (true_value(value)) { + _manifest_list[i]._flags |= SOURCE_EXECUTE; + } + } else { + // check if they have specified a target side executable. this is + // mutually exclusive with a source side exec. + if (ini.get(section_name, "exec_target", value)) { + if (true_value(value)) + _manifest_list[i]._flags |= TARGET_EXECUTE; + } + } + } + + // replace environment variables in the source now... + _manifest_list[i]._source = parser_bits::substitute_env_vars + (_manifest_list[i]._source, false); + + // look for wildcards in the source. + int indy = _manifest_list[i]._source.find("*"); + + // see if they specified a keyword on the command line and if this matches. + // if not we need to abandon this item. + if (!!_keyword && !_manifest_list[i]._keywords.member(_keyword)) { + // their keyword choice didn't match what we were told to use. + noisy_logfile.log(astring("skipping ") + _manifest_list[i]._payload + + " file check; doesn't match keyword \"" + _keyword + "\""); + continue; + } + + // we only access the source file here if it's finalized. we can't do + // this if the target is supposed to be recursive or if it's got a wildcard + // pattern in it. + if (!(_manifest_list[i]._flags & RECURSIVE_SRC) && negative(indy) + && !(_manifest_list[i]._flags & OMIT_PACKING) ) { + // access the source file to get its size. + byte_filer source_file(_manifest_list[i]._source, "rb"); + if (!source_file.good()) { + LOG(astring("could not access the source file for bundling: ") + + _manifest_list[i]._source); + BAIL(69); + } + bool okaysize = get_file_size(_manifest_list[i]._source, + _manifest_list[i]._size, _manifest_list[i].c_filetime); + if (!okaysize || (_manifest_list[i]._size < 0) ) { + // this is a failure, but the function complains about it already. + BAIL(75); + } + } + } + + // patch the manifest list for wildcards and recursive sources. + for (int i = 0; i < _manifest_list.length(); i++) { + bundled_chunk curr = _manifest_list[i]; + + if (!!_keyword && !curr._keywords.member(_keyword)) { + // this item's keyword doesn't match the one we were given, so skip it. + noisy_logfile.log(astring("zapping entry for ") + curr._payload + + "; doesn't match keyword \"" + _keyword + "\""); + _manifest_list.zap(i, i); + i--; // skip back since we eliminated an index. + continue; + } + + if (curr._flags & SET_VARIABLE) { + // we're done working on this. + continue; + } else if (curr._flags & TEST_VARIABLE_DEFINED) { + // this also requires no further effort. + continue; + } else if (curr._flags & RECURSIVE_SRC) { + // handle a recursive style target. + int star_indy = curr._source.find("*"); + if (non_negative(star_indy)) { + // this is currently illegal. we don't allow recursion + wildcards. + LOG(astring("illegal combination of recursion and wildcard: ") + + curr._source); + BAIL(70); + } + // handle the recursive guy. + int ret = patch_recursive_target(curr._source, curr._payload, i); + if (ret != 0) { + LOG(astring("failed during packing of recursive source: ") + + curr._source); + BAIL(72); + } + // take this item out of the picture, since all contents got included. + _manifest_list.zap(i, i); + i--; // skip back since we eliminated an index. + continue; + } else if (curr._flags & SOURCE_EXECUTE) { + // we have massaged the current manifest chunk as much as we can, so now + // we will execute the source item if that was specified. + BASE_LOG(astring("launching ") + curr._source); + if (!!curr._parms) { + curr._parms = parser_bits::substitute_env_vars(curr._parms, false); + BASE_LOG(astring("\tparameters ") + curr._parms); + } + BASE_LOG(astring('-', 76)); + basis::un_int kid; + basis::un_int retval = launch_process::run(curr._source, curr._parms, + launch_process::AWAIT_APP_EXIT, kid); + if (retval != 0) { + LOG(astring("failed to launch process, source=") + curr._source + + ", with parms " + curr._parms); + if (! (curr._flags & IGNORE_ERRORS) ) { + BAIL(92); + } + } + BASE_LOG(astring('-', 76)); + if (curr._flags & OMIT_PACKING) { + // this one shouldn't be included in the package. + _manifest_list.zap(i, i); + i--; // skip back since we eliminated an index. + } + continue; + } else { + // check for a wildcard. + int star_indy = curr._source.find("*"); + if (negative(star_indy)) continue; // simple targets are boring. + // this does have a wildcard in it. let's make sure it's in the right + // place for a wildcard in our scheme. + int slash_indy = curr._source.find('/', curr._source.end(), true); + if (star_indy < slash_indy) { + BASE_LOG(astring("illegal wildcard placement in ") + curr._source); + BASE_LOG(astring(" (the wildcard must be in the last component of the path)")); + BAIL(71); + } + // handle the wildcarded source. + int ret = patch_wildcard_target(curr._source, curr._payload, i); + if (ret != 0) { + LOG(astring("failed during packing of wildcarded source: ") + + curr._source); + BAIL(73); + } + _manifest_list.zap(i, i); + i--; // skip back since we eliminated an index. + continue; + } + } + +#ifdef DEBUG_BUNDLER + if (!final_return) { + // we had a successful run so we can print this stuff out. + LOG("read the following info from manifest:"); + for (int i = 0; i < _manifest_list.length(); i++) { + bundled_chunk &curr = _manifest_list[i]; + BASE_LOG(a_sprintf("(%d) size %d, %s => %s", i, curr._size, + curr._source.s(), curr._payload.s())); + } + } +#endif + + return final_return; +} + +astring bundle_creator::determine_stub_file_and_validate() +{ + FUNCDEF("determine_stub_file_and_validate"); + // define our location to find the unpacking stub program. +//hmmm: make this a command line parameter. +#ifdef __UNIX__ + astring stub_filename("unpacker_stub"); +#endif +#ifdef __WIN32__ + astring stub_filename("unpacker_stub.exe"); +#endif + astring repo_dir = "$PRODUCTION_DIR"; + astring stub_file = parser_bits::substitute_env_vars + (repo_dir + "/binaries/" + stub_filename, false); + if (!filename(stub_file).exists()) { + // we needed to find that to build the bundle. + LOG(astring("could not find unpacking stub file at: ") + stub_file); + return astring::empty_string(); + } + return stub_file; +} + +int bundle_creator::write_stub_and_toc() +{ + FUNCDEF("write_stub_and_toc"); + + astring stub_file = determine_stub_file_and_validate(); + if (!stub_file) return 1; + + // make sure the stub is accessible. + byte_filer stubby(stub_file, "rb"); + if (!stubby.good()) { + FAIL_RETURN(80, astring("could not read the unpacking stub at: ") + stub_file); + } + _stub_size = int(stubby.length()); // get the stub size for later reference. + byte_array whole_stub; + stubby.read(whole_stub, _stub_size + 100); + stubby.close(); + _bundle->write(whole_stub); + + byte_array packed_toc_len; + structures::obscure_attach(packed_toc_len, _manifest_list.length()); + int ret = _bundle->write(packed_toc_len); + if (ret < 0) { + LOG(astring("could not write the TOC length to the bundle: ") + + _output_file); + return 81; + } + + // dump out the manifest list in our defined format. + for (int i = 0; i < _manifest_list.length(); i++) { + bundled_chunk &curr = _manifest_list[i]; +//LOG(a_sprintf("flag %d is %d", i, curr._flags)); + byte_array chunk; + curr.pack(chunk); + if (_bundle->write(chunk) <= 0) { + LOG(a_sprintf("could not write item #%d [%s] to the bundle: ", i, + curr._source.s()) + + _output_file); + return 88; + } + } + + return 0; +} + +int bundle_creator::bundle_sources() +{ + FUNCDEF("bundle_sources"); + // go through all the source files and append them to the bundled output. + file_logger noisy_logfile(application_configuration::make_logfile_name + ("bundle_creator_activity.log")); + for (int i = 0; i < _manifest_list.length(); i++) { + bundled_chunk &curr = _manifest_list[i]; + + if (curr._flags & SET_VARIABLE) { + // all we need to do is keep this in the manifest. + noisy_logfile.log(astring("bundling: variable setting ") + curr._payload + + "=" + curr._parms); + continue; + } else if (curr._flags & TEST_VARIABLE_DEFINED) { + // just remember to test this when running the unpack. + noisy_logfile.log(astring("bundling: test variable ") + curr._payload + + " is defined."); + continue; + } else if (curr._flags & OMIT_PACKING) { + // this one shouldn't be included in the package. + continue; + } + + noisy_logfile.log(astring("bundling: ") + curr._source); + byte_filer source(curr._source, "rb"); + if (!source.good()) { + LOG(a_sprintf("could not read item #%d for the bundle: \"", i) + + curr._source + "\""); + return 98; + } + + byte_array compressed(256 * KILOBYTE); // expand the buffer to start with. + byte_array temp; // temporary read buffer. + + // chew on the file a chunk at a time. this allows us to easily handle + // arbitrarily large files rather than reading their entirety into memory. + int total_written = 0; + do { + int ret = source.read(temp, CHUNKING_SIZE); + if (ret < 0) { + LOG(a_sprintf("failed while reading item #%d: ", i) + curr._source); + return 99; + } + total_written += ret; // add in what we expect to write. + // skip compressing if there's no data. + uLongf destlen = 0; + bool null_chunk = false; + if (ret == 0) { + compressed.reset(); + null_chunk = true; + } else { + compressed.reset(int(0.1 * ret) + ret + KILOBYTE); + // provide some extra space as per zlib instructions. we're giving it + // way more than they request. + destlen = compressed.length(); + // pack the chunks first so we can know sizes needed. + int comp_ret = compress(compressed.access(), &destlen, temp.observe(), + temp.length()); + if (comp_ret != Z_OK) { + LOG(a_sprintf("failed while compressing item #%d: ", i) + + curr._source); + return 99; + } + compressed.zap(destlen, compressed.length() - 1); + } + byte_array just_sizes; + structures::obscure_attach(just_sizes, temp.length()); + // add in the real size. + structures::obscure_attach(just_sizes, int(destlen)); + // add in the packed size. + ret = _bundle->write(just_sizes); + if (ret <= 0) { + LOG(a_sprintf("failed while writing sizes for item #%d: ", i) + + curr._source); + return 93; + } + if (!null_chunk) { + ret = _bundle->write(compressed); + if (ret <= 0) { + LOG(a_sprintf("failed while writing item #%d: ", i) + curr._source); + return 93; + } else if (ret != compressed.length()) { + LOG(a_sprintf("wrote different size for item #%d (tried %d, " + "wrote %d): ", i, compressed.length(), ret) + curr._source); + return 93; + } + } + } while (!source.eof()); +//hmmm: very common code to above size writing. + byte_array just_sizes; + structures::obscure_attach(just_sizes, -1); + structures::obscure_attach(just_sizes, -1); + int ret = _bundle->write(just_sizes); + if (ret <= 0) { + LOG(a_sprintf("failed while writing sentinel of item #%d: ", i) + + curr._source); + return 96; + } + source.close(); + if (total_written != curr._size) { + LOG(a_sprintf("size (%d) disagrees with initial size (%d) for " + "item #%d: ", total_written, curr._size, i) + curr._source); + } + } + noisy_logfile.log(astring("Bundling run ends at ") + time_stamp::notarize(false)); + noisy_logfile.log(astring('-', 76)); + + return 0; +} + +int bundle_creator::finalize_file() +{ + _bundle->close(); + return 0; +} + +int bundle_creator::write_offset() +{ +// FUNCDEF("write_offset"); + byte_filer bun(_output_file, "r+b"); // open the file for updating. + + astring magic_string("muftiloc"); // our sentinel string. + astring temp_string; // data from the file. + + while (!bun.eof()) { + // find the telltale text in the file. + bool found_it = false; // we'll set this to true if we see the string. + int location = 0; // where the sentinel's end is. + for (int i = 0; i < magic_string.length(); i++) { + int ret = bun.read(temp_string, 1); + if (ret <= 0) break; + if (temp_string[0] != magic_string[i]) break; // no match. + if (i == magic_string.end()) { + // we found a match to our string! + found_it = true; + location = int(bun.tell()); +//LOG(a_sprintf("found the sentinel in the file! posn=%d", location)); + } + } + if (!found_it) continue; // keep reading. + bun.seek(location); + byte_array packed_offset; + structures::obscure_attach(packed_offset, _stub_size); +//LOG(astring("pattern of len is:\n") + byte_format::text_dump(packed_offset)); + // write the offset into the current position, which should be just after + // the sentinel's location. + bun.write(packed_offset); +//LOG(a_sprintf("wrote manifest offset before posn=%d", bun.tell())); + break; // done with looking for that pattern. + } + bun.close(); // completely finished now. + + chmod(_output_file.s(), 0766); + // make sure it's an executable file when we're done with it. + + BASE_LOG(astring("done file bundling at ") + time_stamp::notarize(false)); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(bundle_creator, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/applications/bundler/common_bundle.cpp b/nucleus/applications/bundler/common_bundle.cpp new file mode 100644 index 00000000..6d170b59 --- /dev/null +++ b/nucleus/applications/bundler/common_bundle.cpp @@ -0,0 +1,139 @@ +/*****************************************************************************\ +* * +* Name : common bundler definitions * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "common_bundle.h" + +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace filesystem; +using namespace structures; + +manifest_chunk::~manifest_chunk() +{} + +int manifest_chunk::packed_filetime_size() +{ + static file_time hidden_comparison_object; + return hidden_comparison_object.packed_size(); +} + +void manifest_chunk::pack(byte_array &target) const +{ + structures::obscure_attach(target, _size); + _payload.pack(target); + structures::attach(target, _flags); + _parms.pack(target); + _keywords.pack(target); + target += c_filetime; +} + +bool manifest_chunk::unpack(byte_array &source) +{ + if (!structures::obscure_detach(source, _size)) return false; + if (!_payload.unpack(source)) return false; + if (!structures::detach(source, _flags)) return false; + if (!_parms.unpack(source)) return false; + if (!_keywords.unpack(source)) return false; + if (source.length() < 8) return false; + c_filetime = source.subarray(0, 7); + source.zap(0, 7); + return true; +} + +bool manifest_chunk::read_an_int(byte_filer &bundle, un_int &found) +{ +// FUNCDEF("read_an_int"); + byte_array temp; + if (bundle.read(temp, sizeof(int)) != sizeof(int)) return false; + if (!structures::detach(temp, found)) return false; + return true; +} + +bool manifest_chunk::read_an_obscured_int(byte_filer &bundle, un_int &found) +{ +// FUNCDEF("read_an_obscured_int"); + byte_array temp; + if (bundle.read(temp, 2 * sizeof(int)) != 2 * sizeof(int)) return false; + if (!structures::obscure_detach(temp, found)) return false; + return true; +} + +bool manifest_chunk::read_a_filetime(byte_filer &bundle, byte_array &found) +{ +// FUNCDEF("read_a_filetime"); + byte_array temp; + // the trick below only works because we know we have a constant sized packed version + // for the file time. + if (bundle.read(temp, packed_filetime_size()) != packed_filetime_size()) return false; + found = temp; + return true; +} + +astring manifest_chunk::read_a_string(byte_filer &bundle) +{ +// FUNCDEF("read_a_string"); + astring found; + byte_array temp; + // read in the zero-terminated character string. + while (!bundle.eof()) { + // read a single byte out of the file. + if (bundle.read(temp, 1) <= 0) + break; + if (temp[0]) { + // add the byte to the string we're accumulating. + found += temp[0]; + } else { + // this string is done now. + break; + } + } + return found; +} + +bool manifest_chunk::read_manifest(byte_filer &bundle, manifest_chunk &curr) +{ + curr._size = 0; + bool worked = read_an_obscured_int(bundle, curr._size); + if (!worked) + return false; + byte_array temp; + curr._payload = read_a_string(bundle); + if (!curr._payload) return false; + worked = read_an_int(bundle, curr._flags); + if (!worked) + return false; + curr._parms = read_a_string(bundle); + // it's valid for the _parms to be empty. +//if (curr._parms.length()) { printf("parms len=%d are: \"%s\"\n", curr._parms.length(), curr._parms.s()); } + // now get the keywords list, if it exists. + un_int key_elems = 0; // number of keywords. + worked = read_an_obscured_int(bundle, key_elems); // get number of elements. + if (!worked) + return false; + curr._keywords.reset(); + for (int i = 0; i < (int)key_elems; i++) { + astring found = read_a_string(bundle); + if (!found) return false; // not allowed an empty keyword. + curr._keywords += found; + } + worked = read_a_filetime(bundle, curr.c_filetime); + return worked; +} + diff --git a/nucleus/applications/bundler/common_bundle.h b/nucleus/applications/bundler/common_bundle.h new file mode 100644 index 00000000..9e9a57e3 --- /dev/null +++ b/nucleus/applications/bundler/common_bundle.h @@ -0,0 +1,113 @@ +#ifndef COMMON_BUNDLER_DEFS +#define COMMON_BUNDLER_DEFS + +/*****************************************************************************\ +* * +* Name : common bundler definitions * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! Contains some definitions used by both the bundle creator and unpacker. +/*! + Note that this is a heavyweight header and should not be pulled into + other headers. +*/ + +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////// + +//! flags that control special attributes of the packed files. +enum special_bundling_flags { + SOURCE_EXECUTE = 0x2, //!< the file should be executed before bundling. + TARGET_EXECUTE = 0x4, //!< the file should be executed on unbundling. + RECURSIVE_SRC = 0x8, //!< source is a recursive folder. + OMIT_PACKING = 0x10, //!< for a source side exe, do not pack the file. + SET_VARIABLE = 0x20, //!< this item just has a variable assignment. + IGNORE_ERRORS = 0x40, //!< if set, errors in an item will not stop program. + NO_OVERWRITE = 0x80, //!< target file will not be overwritten if exists. + QUIET_FAILURE = 0x100, //!< when errors happen, no popup message happens. + MAKE_BACKUP_FILE = 0x200, //!< save a copy if original file already exists. + TEST_VARIABLE_DEFINED = 0x400 //!< check for required variable's presence. +}; + +//////////////////////////////////////////////////////////////////////////// + +//! we will read the manifest pieces out of our own exe image. +/*! + the manifest chunks provide us with enough information to unpack the + data chunks that come afterward. +*/ + +struct manifest_chunk : public basis::text_formable +{ + basis::un_int _size; //!< the size of the packed file. + basis::astring _payload; //!< guts of the chunk, such as location for file on target or a variable definition. + basis::un_int _flags; //!< uses the special_bundling_flags. + basis::astring _parms; //!< the parameters to pass on the command line. + structures::string_set _keywords; //!< keywords applicable to this item. + basis::byte_array c_filetime; //!< more than enough room for unix file time. + + // note: when flags has SET_VARIABLE, the _payload is the variable + // name to be set and the _parms is the value to use. + + static int packed_filetime_size(); + + //! the chunk is the unit found in the packing manifest in the bundle. + manifest_chunk(int size, const basis::astring &target, int flags, + const basis::astring &parms, const structures::string_set &keywords) + : _size(size), _payload(target), _flags(flags), _parms(parms), + _keywords(keywords), c_filetime(packed_filetime_size()) { + for (int i = 0; i < packed_filetime_size(); i++) c_filetime[i] = 0; + } + + manifest_chunk() : _size(0), _flags(0), c_filetime(packed_filetime_size()) { + //!< default constructor. + for (int i = 0; i < packed_filetime_size(); i++) c_filetime[i] = 0; + } + + virtual ~manifest_chunk(); + + virtual void text_form(basis::base_string &state_fill) const { + state_fill.assign(basis::astring(class_name()) + + basis::a_sprintf(": size=%d payload=%s flags=%x parms=%s", + _size, _payload.s(), _flags, _parms.s())); + } + + DEFINE_CLASS_NAME("manifest_chunk"); + + void pack(basis::byte_array &target) const; //!< streams out into the "target". + bool unpack(basis::byte_array &source); //!< streams in from the "source". + + static bool read_manifest(filesystem::byte_filer &bundle, manifest_chunk &to_fill); + //!< reads a chunk out of the "bundle" and stores it in "to_fill". + /*!< false is returned if the read failed. */ + + static basis::astring read_a_string(filesystem::byte_filer &bundle); + //!< reads a string from the "bundle" file, one byte at a time. + + static bool read_an_int(filesystem::byte_filer &bundle, basis::un_int &found); + //!< reads an integer (4 bytes) from the file into "found". + + static bool read_an_obscured_int(filesystem::byte_filer &bundle, basis::un_int &found); + //!< reads in our obscured packing format for an int, which takes 8 bytes. + + static bool read_a_filetime(filesystem::byte_filer &bundle, basis::byte_array &found); + //!< retrieves packed_filetime_size() byte timestamp from the "bundle". +}; + +//////////////////////////////////////////////////////////////////////////// + +#endif + diff --git a/nucleus/applications/bundler/makefile b/nucleus/applications/bundler/makefile new file mode 100644 index 00000000..1137f980 --- /dev/null +++ b/nucleus/applications/bundler/makefile @@ -0,0 +1,27 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +PROJECT = app_bundle +TYPE = application +SOURCE = common_bundle.cpp +ifeq "$(OMIT_VERSIONS)" "" + SOURCE += bundler_version.rc +endif +DEFINITIONS += __BUILD_STATIC_APPLICATION__=t +TARGETS = bundle_creator.exe +LAST_TARGETS += make_stub +ifeq "$(OP_SYSTEM)" "WIN32" + LOCAL_HEADERS += $(THIRD_PARTY_DIR)/zlib/include + LOCAL_LIBRARIES += $(THIRD_PARTY_DIR)/zlib/lib + LIBS_USED += zlib.lib +endif +ifeq "$(OP_SYSTEM)" "UNIX" + LIBS_USED += z +endif + +include cpp/rules.def + +make_stub: + $(MAKE) -f makefile.stub + diff --git a/nucleus/applications/bundler/makefile.stub b/nucleus/applications/bundler/makefile.stub new file mode 100644 index 00000000..4ab4683e --- /dev/null +++ b/nucleus/applications/bundler/makefile.stub @@ -0,0 +1,24 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +PROJECT = app_bundle_stub +TYPE = application +SOURCE = common_bundle.cpp +ifeq "$(OMIT_VERSIONS)" "" + SOURCE += bundler_version.rc +endif +DEFINITIONS += __BUILD_STATIC_APPLICATION__=t +TARGETS = unpacker_stub.exe +ifeq "$(OP_SYSTEM)" "WIN32" + LIBS_USED += libcmt.lib shlwapi.lib zlib.lib + LOAD_FLAG_PREFIX += -nodefaultlib:msvcrt.lib + COMPILER_FLAGS += -MT + LOCAL_HEADERS += $(THIRD_PARTY_DIR)/zlib/include + LOCAL_LIBRARIES += $(THIRD_PARTY_DIR)/zlib/lib +else + LIBS_USED += z +endif + +include cpp/rules.def + diff --git a/nucleus/applications/bundler/manifest_format.txt b/nucleus/applications/bundler/manifest_format.txt new file mode 100644 index 00000000..20e38a93 --- /dev/null +++ b/nucleus/applications/bundler/manifest_format.txt @@ -0,0 +1,23 @@ + +the unpacking manifest is a structure defined in terms of bytes. +the exe's manifest offset is set to point to the beginning of this structure. + +bytes content +----- ------- +0 => 3 number of chunks in the TOC +4 => 4+N-1 first manifest item, with length N +4+N => 4+N+M-1 second item, with length M +4+N+M =>...etc. + +each bundle chunk has a structure: + +bytes content +----- ------- +0 => 3 size of the data component of the chunk +4 => 4+S-1 the file system target location for this chunk, as a zero + terminated string (of length S). this string comes from the + target defined in the packing manifest. + +then the data starts after the end of the TOC; each chunk occupies the space +declared for it in the manifest. + diff --git a/nucleus/applications/bundler/unpacker_stub.cpp b/nucleus/applications/bundler/unpacker_stub.cpp new file mode 100644 index 00000000..a515d8cc --- /dev/null +++ b/nucleus/applications/bundler/unpacker_stub.cpp @@ -0,0 +1,660 @@ +/*****************************************************************************\ +* * +* Name : unpacker stub program * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "common_bundle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef __UNIX__ + #include +#endif +#ifdef __WIN32__ + #include + #include + #include + #include +#endif + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; +using namespace textual; + +const int CHUNKING_SIZE = 64 * KILOBYTE; + // we'll read this big a chunk from a source file at a time. + +const astring TARGET_WORD = "TARGET"; +const astring LOGDIR_WORD = "LOGDIR"; + +#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) +#define LOG(to_print) STAMPED_EMERGENCY_LOG(program_wide_logger::get(), to_print) + +//#define DEBUG_STUB + // uncomment for noisier version. + +const char *ERROR_TITLE = "An Error Caused Incomplete Installation"; + // used in error messages as the title. + +//////////////////////////////////////////////////////////////////////////// + +class unpacker_stub : public application_shell +{ +public: + unpacker_stub() : application_shell(), _app_name(filename(_global_argv[0]).basename()) {} + DEFINE_CLASS_NAME("unpacker_stub"); + + int print_instructions(); + + virtual int execute(); + +private: + astring _app_name; + array _manifest; //!< the list of chunks to unpack. + string_table _variables; //!< our list of variable overrides. +}; + +//////////////////////////////////////////////////////////////////////////// + +void show_message(const astring &msg, const astring &title) +{ +#ifndef __WIN32__ + BASE_LOG(title); + BASE_LOG(astring('-', title.length())); + BASE_LOG(msg); +#else + MessageBox(0, to_unicode_temp(msg), to_unicode_temp(title), + MB_OK | MB_ICONINFORMATION); +#endif +} + +int unpacker_stub::print_instructions() +{ + a_sprintf msg("\ + %s: This program unpacks its contents into the locations\n\ + specified at packing time. The --target flag can be used to specify a\n\ +different %s directory for the installation (for components that use\n\ +the default %s variable to specify their install folder).\n\ + One can also pass a --keyword flag to specify a keyword; the files in the\n\ +bundle that are marked with that keyword will be installed, but files that\n\ +are missing the keyword will not be.\n\ + Further, variables can be overridden on the command line in the\n\ +form: X=Y.\n\n\ +The line below uses all these parameters as an example:\n\n\ + %s --target c:\\Program Files\\gubernator --keyword dlls_only SILENT=true\n\ +\n\ +Additional Notes:\n\ +\n\ + One helpful variable is \"%s\". This is where the unpacking log file\n\ +will be written to. The default is \"~/logs\" (or \"$TMP/logs\" on win32)\n\ +until overridden.\n\ +\n", _app_name.s(), TARGET_WORD.s(), TARGET_WORD.s(), _app_name.s(), LOGDIR_WORD.s()); + show_message(msg, "Unpacking Instructions"); + return 12; +} + +// creates a unique backup file name, if it can. +// we assume that this file already exists, but we want to check for +// our backup file naming scheme in case we already backed this up +// some previous time. +astring find_unique_backup_name(astring original_file) +{ +const int MAXIMUM_BACKUPS = 200; + + for (int i = 0; i < MAXIMUM_BACKUPS; i++) { + filename target_file = original_file + a_sprintf(".%04d", i); + if (target_file.exists()) { +//BASE_LOG(astring("bkup already here: ") + target_file); + continue; + } + // this file is okay to use. + return target_file; + } + return ""; // nothing found. +} + + +// the string embedded into the array is not mentioned anywhere else in this +// program, which should allow the packer to find it and fix the manifest +// size. the keyword is: "muftiloc", and the first bytes that can be +// overwritten are its beginning offset plus its length of 8 chars. there +// is room for 8 bytes after the tag, but currently the first 4 are used as +// a 32 bit offset. +abyte MANIFEST_OFFSET_ARRAY[] + = { 'm', 'u', 'f', 't', 'i', 'l', 'o', 'c', 0, 0, 0, 0, 0, 0, 0, 0 }; + +int unpacker_stub::execute() +{ +#ifdef ADMIN_CHECK + #ifdef __WIN32__ + if (IsUserAnAdmin()) { + ::MessageBox(0, to_unicode_temp("IS admin in bundler"), to_unicode_temp("bundler"), MB_OK); + } else { + ::MessageBox(0, to_unicode_temp("NOT admin in bundler"), to_unicode_temp("bundler"), MB_OK); + } + #endif +#endif + + command_line cmds(_global_argc, _global_argv); + + int indy = 0; + if (cmds.find('?', indy)) return print_instructions(); + if (cmds.find("?", indy)) return print_instructions(); + + // make sure we provide the same services as the bundle creator for the + // default set of variables. +#ifndef __WIN32__ + environment::set("EXE_END", ""); // executable file ending. + environment::set("DLL_START", "lib"); // dll file prefix. + environment::set("DLL_END", ".so"); // dll file ending. +#else + environment::set("EXE_END", ".exe"); + environment::set("DLL_START", ""); + environment::set("DLL_END", ".dll"); +#endif + + // set TARGET directory if passed on the command line, + bool provided_target = false; // true if the command line specified target. + astring target; + cmds.get_value("target", target); + if (!target) { +/* no, this is wrong headed. make them supply a target if there is + * no default target provided in the bundle. + // a bogus default is provided if they don't specify the destination. + target = environment::get("TMP") + "/unbundled"; +*/ + provided_target = false; + } else { +//LOG(astring("target is now ") + target); + environment::set(TARGET_WORD, target); + provided_target = true; + } + + { + astring logdir = environment::get(LOGDIR_WORD); +#ifdef __WIN32__ + if (!logdir) { + logdir = environment::get("TMP") + "/logs"; + environment::set(LOGDIR_WORD, logdir); + } +#else + if (!logdir) { + astring homedir = environment::get("HOME"); + logdir = homedir + "/logs"; + environment::set(LOGDIR_WORD, logdir); + } +#endif + } + + astring keyword; // set if we were given a keyword on cmd line. + cmds.get_value("keyword", keyword); + + astring vars_set; // we will document the variables we saw and show later. + + for (int x = 0; x < cmds.entries(); x++) { + command_parameter curr = cmds.get(x); + if (curr.type() != command_parameter::VALUE) continue; // skip it. + if (curr.text().find('=', 0) < 0) continue; // no equals character. + variable_tokenizer t; + t.parse(curr.text()); + if (!t.symbols()) continue; // didn't parse right. + astring var = t.table().name(0); + astring value = t.table()[0]; + vars_set += astring("variable set: ") + var + "=" + value + + parser_bits::platform_eol_to_chars(); + if (var == TARGET_WORD) { + provided_target = true; + } +//hmmm: handle LOGDIR passed as variable this way also! + _variables.add(var, value); + environment::set(var, value); + } + + // get the most up to date version of the variable now. + astring logdir = environment::get(LOGDIR_WORD); + + astring appname = filename(application_configuration::application_name()).rootname(); + + astring logname = logdir + "/" + appname + ".log"; +// log_base *old_log = set_PW_logger_for_combo(logname); + standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname)); + WHACK(old_log); + + BASE_LOG(astring('#', 76)); + BASE_LOG(appname + " command-line parameters:"); + BASE_LOG(cmds.text_form()); + BASE_LOG(astring('#', 76)); + + BASE_LOG(vars_set); + +#ifdef __WIN32__ + // create a window so that installshield won't barf. this is only needed + // on windows when using this as a prerequisite for installshield. + window_handle f_window = create_simplistic_window("temp_stubby_class", + "stubby window title"); +#endif + + // read position for manifest offset out of our array. + byte_array temp_packed(2 * sizeof(int), MANIFEST_OFFSET_ARRAY + 8); + un_int manifest_offset; + if (!structures::obscure_detach(temp_packed, manifest_offset)) { + show_message(astring("could not read manifest offset in: ") + _global_argv[0], + ERROR_TITLE); + return 24; + } + + filename this_exe(_global_argv[0]); + if (!this_exe.exists()) { + show_message(astring("could not access this exe image: ") + this_exe.raw(), + ERROR_TITLE); + return 23; + } + + // start reading the manifest... + byte_filer our_exe(this_exe, "rb"); + our_exe.seek(manifest_offset); // go to where the manifest starts. + + // get number of chunks in manifest as the first bytes. + if (our_exe.read(temp_packed, 2 * sizeof(int)) <= 0) { + show_message(astring("could not read the manifest length in: ") + + this_exe.raw(), ERROR_TITLE); + return 26; + } + un_int item_count; + structures::obscure_detach(temp_packed, item_count); +//check result of detach! + _manifest.insert(0, item_count); // add enough spaces for our item list. + + // read each item definition out of the manifest now. + for (int i = 0; i < (int)item_count; i++) { + manifest_chunk &curr = _manifest[i]; + bool worked = manifest_chunk::read_manifest(our_exe, curr); + +#ifdef DEBUG_STUB + astring tmpork; + curr.text_form(tmpork); + LOG(a_sprintf("item %d: ", i) + tmpork); +#endif + + if (!worked) { + show_message(a_sprintf("could not read chunk for item #%d [%s]: ", i, + curr._payload.s()) + + this_exe.raw(), ERROR_TITLE); + return 86; + } + } + +#ifdef DEBUG_STUB + LOG("read the following info from manifest:"); + astring temp; + for (int i = 0; i < _manifest.length(); i++) { + manifest_chunk &curr = _manifest[i]; + temp += a_sprintf("(%d) size %d, %s\n", i, curr._size, + curr._payload.s()); + } + critical_events::alert_message(temp, "manifest contents"); +#endif + + // now we should be just after the last byte of the manifest, at the + // first piece of data. we should read each chunk of data out and store + // it where it's supposed to go. + for (int festdex = 0; festdex < _manifest.length(); festdex++) { + manifest_chunk &curr = _manifest[festdex]; + int size_left = curr._size; + + // patch in our environment variables. + curr._payload = parser_bits::substitute_env_vars(curr._payload, false); + curr._parms = parser_bits::substitute_env_vars(curr._parms, false); +#ifdef DEBUG_STUB + BASE_LOG(astring("processing ") + curr._payload + + a_sprintf(", size=%d, flags=%d", curr._size, curr._flags)); + if (!!curr._parms) + BASE_LOG(astring(" parms: ") + curr._parms); +#endif + + // see if they specified a keyword on the command line. + bool keyword_good = true; + if (!!keyword && !curr._keywords.member(keyword)) { + // their keyword choice didn't match what we were pickled with. + keyword_good = false; +//BASE_LOG(astring("skipping ") + curr._payload + " for wrong keyword " + keyword); + } + + if (curr._flags & SET_VARIABLE) { + if (keyword_good) { + // this is utterly different from a real target. we just set the + // variable and move on. + if (provided_target && (curr._payload == TARGET_WORD) ) { + BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms + + ": was provided explicitly as " + target); + } else if (_variables.find(curr._payload)) { + BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms + + ": was provided on command line."); + } else { + BASE_LOG(astring("setting ") + curr._payload + "=" + curr._parms); + environment::set(curr._payload, curr._parms); + + // special code for changing logging directory midstream. + if (curr._payload == LOGDIR_WORD) { + astring logdir = curr._parms; + astring appname = filename(application_configuration::application_name()).rootname(); + astring logname = logdir + "/" + appname + ".log"; + standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname)); +/// log_base *old_log = set_PW_logger_for_combo(logname); + WHACK(old_log); + } + if (curr._payload == TARGET_WORD) { + // well we've now seen this defined. + provided_target = true; + } + } + } + continue; + } else if (curr._flags & TEST_VARIABLE_DEFINED) { + if (keyword_good) { + astring var_value = environment::get(curr._payload); + if (var_value.empty()) { + BASE_LOG(astring("assertion failed: ") + curr._payload + " is not defined!"); + show_message(a_sprintf("failed test for variable %s: it is " + "*not* defined, for item #%d.", curr._payload.s(), festdex), + ERROR_TITLE); + return 98; + } + BASE_LOG(astring("assertion succeeded: ") + curr._payload + " defined as " + var_value); + } + continue; + } + + if (! (curr._flags & OMIT_PACKING) ) { + // this one has a payload, so install it now if appropriate. + if (!provided_target) { +//error! + BASE_LOG(astring("No TARGET has been specified; please provide one on the command line.") + parser_bits::platform_eol_to_chars()); + return print_instructions(); + } + + // make sure that the directories needed are present for the outputs. + filename target_dir = filename(curr._payload).dirname(); + + if (keyword_good && !target_dir.exists() + && !directory::recursive_create(target_dir)) { + LOG(a_sprintf("failed to create directory %s for item #%d: ", + target_dir.raw().s(), festdex) + curr._payload); + } + + // test whether they wanted to allow overwriting. + if (curr._flags & NO_OVERWRITE) { + filename target_file(curr._payload); + if (target_file.exists()) { + BASE_LOG(astring("not overwriting existing ") + curr._payload); + keyword_good = false; + } + } + + // see if this is supposed to be backed up before installation. + if (curr._flags & MAKE_BACKUP_FILE) { + filename target_file(curr._payload); + if (target_file.exists()) { + astring new_file_name = find_unique_backup_name(curr._payload); + if (!new_file_name) { + BASE_LOG(astring("failed to calculate new filename for ") + curr._payload); + keyword_good = false; // cancel the overwrite, couldn't backup. + } else { + // make a backup of the file by moving the old file name to the + // new file name, which should be unique, and then the current + // name is all clear to be written as a new file. +// BASE_LOG(astring("backing up ") + curr._payload + " --> " + new_file_name); + int retval = rename(curr._payload.s(), new_file_name.s()); + if (retval) { + BASE_LOG(astring("failed to rename ") + curr._payload + " as " + new_file_name); + keyword_good = false; // cancel the overwrite, couldn't backup. + } + } + } + } + + byte_filer *targo = NIL; + if (keyword_good) targo = new byte_filer(curr._payload, "wb"); + byte_array uncompressed(256 * KILOBYTE); // fluff it out to begin with. + byte_array temp(256 * KILOBYTE); + + bool first_read = true; + // true if there haven't been any reads of the file before now. + bool too_tiny_complaint_already = false; + // becomes true if we complain about the file's size being larger than + // expected. this allows us to only complain once about each file. + + // read a chunk at a time out of our exe image and store it into the + // target file. + while (first_read || !our_exe.eof()) { + first_read = false; + un_int real_size = 0, packed_size = 0; + // read in the real size from the file. + bool worked = manifest_chunk::read_an_obscured_int(our_exe, real_size); + if (!worked) { + show_message(a_sprintf("failed while reading real size " + "for item #%d: ", festdex) + curr._payload, ERROR_TITLE); + return 99; + } + // read in the packed size now. + worked = manifest_chunk::read_an_obscured_int(our_exe, packed_size); + if (!worked) { + show_message(a_sprintf("failed while reading packed size " + "for item #%d: ", festdex) + curr._payload, ERROR_TITLE); + return 99; + } + + // make sure we don't eat the whole package--did the file end? + if ( (real_size == -1) && (packed_size == -1) ) { + // we've hit our sentinel; we've already unpacked all of this file. + break; + } + +#ifdef DEBUG_STUB + BASE_LOG(a_sprintf("chunk packed_size=%d, real_size=%d", packed_size, + real_size)); +#endif + + // now we know how big our next chunk is, so we can try reading it. + if (packed_size) { + int ret = our_exe.read(temp, packed_size); + if (ret <= 0) { + show_message(a_sprintf("failed while reading item #%d: ", festdex) + + curr._payload, ERROR_TITLE); + return 99; + } else if (ret != packed_size) { + show_message(a_sprintf("bad trouble ahead, item #%d had different " + " size on read (expected %d, got %d): ", festdex, packed_size, + ret) + curr._payload, ERROR_TITLE); + } + + uncompressed.reset(real_size + KILOBYTE); // add some for paranoia. + uLongf destlen = uncompressed.length(); + int uncomp_ret = uncompress(uncompressed.access(), &destlen, + temp.observe(), packed_size); + if (uncomp_ret != Z_OK) { + show_message(a_sprintf("failed while uncompressing item #%d: ", + festdex) + curr._payload, ERROR_TITLE); + return 99; + } + + if (int(destlen) != real_size) { + LOG(a_sprintf("got a different unpacked size for item #%d: ", + festdex) + curr._payload); + } + + // update the remaining size for this data chunk. + size_left -= real_size; + if (size_left < 0) { + if (!too_tiny_complaint_already) { + LOG(a_sprintf("item #%d was larger than expected (non-fatal): ", + festdex) + curr._payload); + too_tiny_complaint_already = true; + } + } + // toss the extra bytes out. + uncompressed.zap(real_size, uncompressed.length() - 1); + + if (targo) { + // stuff the data we read into the target file. + ret = targo->write(uncompressed); + if (ret != uncompressed.length()) { + show_message(a_sprintf("failed while writing item #%d: ", festdex) + + curr._payload, ERROR_TITLE); + return 93; + } + } + } + } + if (targo) targo->close(); + WHACK(targo); + // the file's written, but now we slap it's old time on it too. + file_time t; + if (!t.unpack(curr.c_filetime)) { + show_message(astring("failed to interpret timestamp for ") + + curr._payload, ERROR_TITLE); + return 97; + } + // put the timestamp on the file. + t.set_time(curr._payload); +// utimbuf held_time; +// held_time.actime = t.raw(); +// held_time.modtime = t.raw(); +// // put the timestamp on the file. +// utime(curr._payload.s(), &held_time); + } + + // now that we're pretty sure the file exists, we can run it if needed. + if ( (curr._flags & TARGET_EXECUTE) && keyword_good) { + // change the mode on the target file so we can execute it. + chmod(curr._payload.s(), 0766); + astring prev_dir = application_configuration::current_directory(); + + BASE_LOG(astring("launching ") + curr._payload); + if (!!curr._parms) + BASE_LOG(astring(" with parameters: ") + curr._parms); + BASE_LOG(astring('-', 76)); + + basis::un_int kid; + basis::un_int retval = launch_process::run(curr._payload, curr._parms, + launch_process::AWAIT_APP_EXIT | launch_process::HIDE_APP_WINDOW, kid); + if (retval != 0) { + if (! (curr._flags & IGNORE_ERRORS) ) { + if (curr._flags & QUIET_FAILURE) { + // no message box for this, but still log it. + LOG(astring("failed to launch process, targ=") + + curr._payload + " with parms " + curr._parms + + a_sprintf(" error=%d", retval)); + } else { + show_message(astring("failed to launch process, targ=") + + curr._payload + " with parms " + curr._parms + + a_sprintf(" error=%d", retval), ERROR_TITLE); + } + return retval; // pass along same exit value we were told. + } else { + LOG(astring("ignoring failure to launch process, targ=") + + curr._payload + " with parms " + curr._parms + + a_sprintf(" error=%d", retval)); + } + } + + chdir(prev_dir.s()); // reset directory pointer, just in case. + + BASE_LOG(astring('-', 76)); + } + + } + +#ifdef __WIN32__ + whack_simplistic_window(f_window); +#endif + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +HOOPLE_MAIN(unpacker_stub, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/applications/bundler/version.ini b/nucleus/applications/bundler/version.ini new file mode 100644 index 00000000..18196156 --- /dev/null +++ b/nucleus/applications/bundler/version.ini @@ -0,0 +1,6 @@ +[version] +description = Application Bundler +root = bundler +name = Bundler +extension = exe + diff --git a/nucleus/applications/example_application/example_application.cpp b/nucleus/applications/example_application/example_application.cpp new file mode 100644 index 00000000..c6a27913 --- /dev/null +++ b/nucleus/applications/example_application/example_application.cpp @@ -0,0 +1,137 @@ +////////////// +// Name : Simple Application Example +// Author : Chris Koeritz +////////////// +// Copyright (c) 2006-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +//! An example of a bare-bones hoople application. +/*! + This application provides a very simple example for how we create programs + based on the HOOPLE code. +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +//using namespace processes; +using namespace structures; +using namespace unit_test; + +const int CHECKING_INTERVAL = 4 * SECOND_ms; + // this many milliseconds elapses between checks on shutdown conditions. + +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + // define a macro that will send diagnostic output to the app's logger. + +////////////// + +class application_example : virtual public unit_base, virtual public application_shell +{ +public: + application_example(); + ~application_example(); + + DEFINE_CLASS_NAME("application_example"); + + bool already_running(); + //!< true if this program is already running. + + virtual void handle_timer(); + //!< called by timer events from anchor window. + + virtual void handle_startup(); + //!< begins our service's activity. + + virtual void handle_shutdown(); + //!< catches the graceful shutdown so our objects get closed normally. + + virtual int execute(); + //!< the root of the program's activity. + + int print_instructions(); + //!< describes the available command line options for this program. + +private: + singleton_application _app_lock; //!< our inter-application synchronizer. +}; + +////////////// + +application_example::application_example() +: application_shell(), + _app_lock(static_class_name()) +{} + +application_example::~application_example() +{} + +int application_example::print_instructions() +{ + FUNCDEF("print_instructions"); + LOG("no instructions at this time."); + return 1; +} + +void application_example::handle_startup() +{ + FUNCDEF("handle_startup"); + LOG("starting up now."); +} + +void application_example::handle_shutdown() +{ + FUNCDEF("handle_shutdown"); + LOG("shutting down now."); +} + +void application_example::handle_timer() +{ + FUNCDEF("handle_timer"); + LOG("timer blip."); +} + +bool application_example::already_running() +{ return _app_lock.already_running(); } + +int application_example::execute() +{ + FUNCDEF("execute"); + command_line cmds(_global_argc, _global_argv); + +//hmmm: test for command line options that are supported. + + // make sure this app is not running already. + if (already_running()) { + return 0; + } + +//need anchor window online for this. +// anchor_window::launch(*this, GET_INSTANCE_HANDLE(), class_name(), CHECKING_INTERVAL); + + ASSERT_EQUAL(astring(class_name()), astring("application_example"), + "simple application name check"); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(application_example, ) + diff --git a/nucleus/applications/example_application/makefile b/nucleus/applications/example_application/makefile new file mode 100644 index 00000000..04094dbc --- /dev/null +++ b/nucleus/applications/example_application/makefile @@ -0,0 +1,13 @@ +CONSOLE_MODE = t + +include cpp/variables.def + +PROJECT = example_application +TYPE = application +SOURCE = +TARGETS = example_application.exe +LOCAL_LIBS_USED = application unit_test loggers configuration processes filesystem textual \ + structures timely basis + +include cpp/rules.def + diff --git a/nucleus/applications/load_test_tools/makefile b/nucleus/applications/load_test_tools/makefile new file mode 100644 index 00000000..152b9c93 --- /dev/null +++ b/nucleus/applications/load_test_tools/makefile @@ -0,0 +1,13 @@ +CONSOLE_MODE = t + +include cpp/variables.def + +PROJECT = load_test_tools +TYPE = application +SOURCE = +TARGETS = memory_hog.exe +LOCAL_LIBS_USED = application unit_test loggers configuration processes filesystem textual \ + structures timely basis + +include cpp/rules.def + diff --git a/nucleus/applications/load_test_tools/memory_hog.cpp b/nucleus/applications/load_test_tools/memory_hog.cpp new file mode 100644 index 00000000..ab24468b --- /dev/null +++ b/nucleus/applications/load_test_tools/memory_hog.cpp @@ -0,0 +1,176 @@ +////////////// +// Name : memory_hog +// Author : Chris Koeritz +////////////// +// Copyright (c) 2012-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +//! This application lives to grub up lots of memory. +/*! + You can specify how much memory the application should consume. + Occasionally it will riffle through its hamstered away memory to try to + ensure that it stays in real memory rather than virtual memory. +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +//using namespace processes; +using namespace structures; +using namespace unit_test; + +const int MEMORY_CHECKING_INTERVAL = 0.5 * SECOND_ms; + // this many milliseconds elapses between checks on shutdown conditions. + +#define BASE_LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + // define a macro that will send diagnostic output to the app's logger. + +////////////// + +class application_example : virtual public unit_base, virtual public application_shell +{ +public: + application_example(); + ~application_example(); + + DEFINE_CLASS_NAME("application_example"); + + virtual void handle_timer(); + //!< called by timer events from anchor window. + + virtual void handle_startup(); + //!< begins our service's activity. + + virtual void handle_shutdown(); + //!< catches the graceful shutdown so our objects get closed normally. + + virtual int execute(); + //!< the root of the program's activity. + + int print_instructions(); + //!< describes the available command line options for the ULS. + +private: + singleton_application _app_lock; //!< our inter-application synchronizer. +}; + +////////////// + +application_example::application_example() +: application_shell(), + _app_lock(static_class_name()) +{} + +application_example::~application_example() +{} + +int application_example::print_instructions() +{ + FUNCDEF("print_instructions"); + BASE_LOG("\ +\nUsage: memory_hog {memorySize}\n\ +\n\ + This application will consume a requested amount of memory until either\n\ +(1) the applications is interrupted or terminated, or (2) the application\n\ +is no longer given memory when it requests it (whether due to account limits\n\ +or to a lack of remaining allocatable memory), or (3) the requested amount\n\ +has been allocated.\n\ + The application will then sit back idly and occasionally riffle through its\n\ +big bag of memory in an attempt to keep most of it in physical memory rather\n\ +than virtual memory.\n\ + This is not a hostile act unless used unwisely; this program is intended\n\ +to get an unloaded machine into a predictable memory state.\n\ + **DO NOT use this program without system administrator permission.**" +); + return 1; +} + +void application_example::handle_startup() +{ + FUNCDEF("handle_startup"); + LOG("starting up now."); +} + +void application_example::handle_shutdown() +{ + FUNCDEF("handle_shutdown"); + LOG("shutting down now."); +} + +void application_example::handle_timer() +{ + FUNCDEF("handle_timer"); + LOG("timer blip."); +} + +int application_example::execute() +{ + FUNCDEF("execute"); + command_line cmds(_global_argc, _global_argv); + +LOG(a_sprintf("cmd line has %d entries", cmds.entries())); + + if (cmds.entries() < 1) { + BASE_LOG("You need to provide a number on the command line...."); + return print_instructions(); + } + + astring size_as_text = cmds.get(0).text(); + +LOG(a_sprintf("hoo hah! got a text thingy of: %s", size_as_text.s())); + + if (size_as_text.find('.') < 0) { + // we don't see this as floating point, but we will move on as if it's + // an integer and can become floating point. + size_as_text += ".0"; + } + + double how_fat = size_as_text.convert(double(0.0)); + +LOG(a_sprintf("got a number from user of: %f", how_fat)); + +//okay, now that we've got that handled, +// we want to have a hash table of byte arrays. +// int hash or whatever? we want it indexable and fast access on a unique id. +// that will be our consumption tactic. +// we will allocate some chunk size in the byte arrays. maybe max of a meg. +// then we just add as many byte arrays as we need to to get to the requested amount. +// don't just allocate them all at the same size; vary it a bunch, like say 20% of +// our maximum allotance. +// so randomly pull memory until we get what we want. +// if we get a memory failure, slack off until we can try to get more. +// if we cannot get memory within certain number of failures, report this and bail out. +// we do not want to crash the machine. +// once we've got our memory, start playing with it. +// every so often, +// pick a random number of items to play with, +// go to each item (which is a byte array), +// pick a random segment of the byte array to look at, +// read the contents of the memory there. that's it, nothing stressful. +// repeat the memory play until forever. +// enhancement, allow them to provide a run time. time out after that elapses. + + return 0; +} + +////////////// + +HOOPLE_MAIN(application_example, ) + diff --git a/nucleus/applications/makefile b/nucleus/applications/makefile new file mode 100644 index 00000000..582277c0 --- /dev/null +++ b/nucleus/applications/makefile @@ -0,0 +1,7 @@ +include variables.def + +PROJECT = core_applications +BUILD_BEFORE = bookmark_tools bundler example_application nechung utilities + +include rules.def + diff --git a/nucleus/applications/nechung/cgi_nechung.cpp b/nucleus/applications/nechung/cgi_nechung.cpp new file mode 100644 index 00000000..a7478245 --- /dev/null +++ b/nucleus/applications/nechung/cgi_nechung.cpp @@ -0,0 +1,155 @@ +/*****************************************************************************\ +* * +* Name : CGI nechung * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1997-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! @file cgi_nechung.cpp Spits out a CGI appropriate chunk of text with a fortune in it. + +#include "nechung_oracle.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +//using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; + +#undef LOG +#define LOG(s) program_wide_logger::get().log(s, 0) + +////HOOPLE_STARTUP_CODE; +//hmmm: need this back when things are ready! + +#define DEFAULT_FORTUNE_FILE "fortunes.dat" + +int main(int argc, char *argv[]) +{ + SETUP_CONSOLE_LOGGER; + + astring name; + astring index; + if (argc > 1) { + // use the first command line argument. + name = argv[1]; + } else { + // if nothing on the command line, then use our defaults. + name = environment::get("NECHUNG"); + // first try the environment variable. + if (!name) name = DEFAULT_FORTUNE_FILE; + // next, use the hardwired default. + } + + if (name.length() < 5) { + LOG(astring("nechung:: file name is too short (") + name + ")."); + return 1; + } + filename index_file_name(name); + astring extension(index_file_name.extension()); + int end = index_file_name.raw().end(); +#ifdef DEBUG_NECHUNG + LOG(astring("fortune filename is ") + name); + LOG(astring("extension is ") + extension); +#endif + astring tmp = index_file_name; + tmp.zap( (end + 1) - extension.length(), end); + tmp += "idx"; + astring base_part = filename(tmp).basename(); + index_file_name = environment::get("TMP") + "/" + base_part; +#ifdef DEBUG_NECHUNG + LOG(astring("index file is ") + index_file_name); +#endif + index = index_file_name; + + nechung_oracle some_fortunes(name, index); + // send the header for html text. + printf("content-type: text/html\n\n"); + // send the preliminary gunk. + printf("\n"); +//old text color #33ccff + printf("\n"); + printf("\n"); + + astring to_show = some_fortunes.pick_random(); + int line_posn = 0; + for (int i = 0; i < to_show.length(); i++) { + if (to_show[i] == ' ') { + // spaces get translated to one non-breaking space. + printf(" "); + line_posn++; + } else if (to_show[i] == '\t') { + // tabs get translated to positioning at tab stops based on eight. + int to_add = 8 - line_posn % 8; + for (int j = 0; j < to_add; j++) printf(" "); + line_posn += to_add; + } else if (to_show[i] == '\r') + continue; + else if (to_show[i] == '\n') { + printf("
    %c", to_show[i]); + line_posn = 0; + } else { + printf("%c", to_show[i]); + line_posn++; + } + } + printf("\n"); + printf("
    \n"); + printf("
    \n"); + printf("\n"); + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/applications/nechung/example.txt b/nucleus/applications/nechung/example.txt new file mode 100644 index 00000000..4a08ef93 --- /dev/null +++ b/nucleus/applications/nechung/example.txt @@ -0,0 +1,76 @@ +/*****************************************************************************\ +* * +* Name : nechung database format sample * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Describes the database format. The "fields" can be any kind of text, * +* including special characters. The one reserved term is a line containing * +* a single tilde character ('~') followed by the appropriate line ending. * +* That marks the beginning of the next record. Nechung will automatically * +* index the file based on these separators whenever the database changes. * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ +~ +Om Muni Muni Maha Muni Yea Swaha +~ +Gun dro nga tang yul nge nga +Gewa ju jik dza nyon druk +Nye nyon nyi shu shen gyur shi +Sem chung nga jik di dak o. +~ +DEVIL +EVIL +GOOD +GOD +~ +Nam Myoho Renge Kyo +~ +Om Mani Padme Hum +~ +Om Shanthi Om +~ +Om A Ra Ba Tsa Na Di +~ +The man in whom Tao +Acts without impediment +Harms no other being +By his actions +Yet he does not know himself +To be "kind," to be "gentle" +~ +When an archer is shooting for nothing +He has all his skill. +If he shoots for a brass buckle +He is already nervous. +If he shoots for a prize of gold +He goes blind +Or sees two targets-- +He is out of his mind! + +His skill has not changed. But the prize +Divides him. He cares. +He thinks more of winning +Than of shooting-- +And the need to win +Drains him of power. +~ +By giving, resources; by ethics, bliss +~ +what you don't see is what you get when you don't look +~ +this bell's knelling is never quelled, + while service is rendered, +tin staccato splattered over cupric strands, + spraying crazed meaning to distant lands. +what is it? +~ +To conquer oneself is a greater task than conquering others diff --git a/nucleus/applications/nechung/makefile b/nucleus/applications/nechung/makefile new file mode 100644 index 00000000..484bf88e --- /dev/null +++ b/nucleus/applications/nechung/makefile @@ -0,0 +1,13 @@ +CONSOLE_MODE = t + +include cpp/variables.def + +PROJECT = nechung +TYPE = application +SOURCE = nechung_oracle.cpp nechung_version.rc +DEFINITIONS += __BUILD_STATIC_APPLICATION__ +UNDEFINITIONS += ENABLE_MEMORY_HOOK ENABLE_CALLSTACK_TRACKING +TARGETS = nechung.exe cgi_nechung.exe + +include cpp/rules.def + diff --git a/nucleus/applications/nechung/nechung.cpp b/nucleus/applications/nechung/nechung.cpp new file mode 100644 index 00000000..5c93f7c6 --- /dev/null +++ b/nucleus/applications/nechung/nechung.cpp @@ -0,0 +1,119 @@ +/*****************************************************************************\ +* * +* Name : nechung console application * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*****************************************************************************/ + +//! @file nechung.cpp The application base for the Nechung Oracle Program (NOP). + +#include "nechung_oracle.h" + +#include +#include +#include +#include +#include +#include + +//using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; + +///HOOPLE_STARTUP_CODE; +//hmmm: missing. + +#undef LOG +#define LOG(s) program_wide_logger::get().log((s), 0) +//hmmm: need ALWAYS_PRINT back in there! + +#define DEFAULT_FORTUNE_FILE "fortunes.dat" + +int main(int argc, char *argv[]) +{ + SETUP_CONSOLE_LOGGER; + + astring name; + astring index; + if (argc > 1) { + // use the first command line argument. + name = argv[1]; + } else { + // if nothing on the command line, then use our defaults. + name = environment::get("NECHUNG"); + // first try the environment variable. + if (!name) name = DEFAULT_FORTUNE_FILE; + // next, use the hardwired default. + } + + if (name.length() < 5) { + LOG(astring("nechung:: file name is too short (") + name + ")."); + return 1; + } + filename index_file_name(name); + astring extension(index_file_name.extension()); + int end = index_file_name.raw().end(); +#ifdef DEBUG_NECHUNG + LOG(astring("fortune filename is ") + name); + LOG(astring("extension is ") + extension); +#endif + astring tmp = index_file_name; + tmp.zap( (end + 1) - extension.length(), end); + tmp += "idx"; + astring base_part = filename(tmp).basename(); + index_file_name = environment::get("TMP") + "/" + base_part; +#ifdef DEBUG_NECHUNG + LOG(astring("index file is ") + index_file_name); +#endif + index = index_file_name; + + nechung_oracle some_fortunes(name, index); + some_fortunes.display_random(); + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/applications/nechung/nechung_oracle.cpp b/nucleus/applications/nechung/nechung_oracle.cpp new file mode 100644 index 00000000..fe34e1a9 --- /dev/null +++ b/nucleus/applications/nechung/nechung_oracle.cpp @@ -0,0 +1,259 @@ +/*****************************************************************************\ +* * +* Name : nechung_oracle * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*****************************************************************************/ + +#include "nechung_oracle.h" + +#include +#include +#include +#include +#include + +#include +#include + +//using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; + +#undef LOG +#define LOG(s) program_wide_logger::get().log(s, 0) +///hmmm: fix filter value to be ALWAYS_PRINT! + +const int MAX_LINE_LENGTH = 2048; + +nechung_oracle::nechung_oracle(const astring &nechung_filename, + const astring &index_filename) +: c_randomizer(), + c_filename_held(nechung_filename), + c_index_held(index_filename), + c_number_of_fortunes(0) +{ parse_file(); } + +nechung_oracle::~nechung_oracle() {} + +void nechung_oracle::parse_file() +{ + FUNCDEF("parse_file"); + // below is code for comparing dates on the fortune file and the index file. + byte_filer fortune_file(c_filename_held.s(), "rb"); +#ifdef DEBUG_NECHUNG + LOG(astring("filename=") + c_filename_held + " idx file=" + c_index_held); +#endif + if (!fortune_file.good()) + non_continuable_error(class_name(), func, "Cannot open fortune file."); + + byte_array buffer(MAX_LINE_LENGTH + 1); + // used throughout parsing for line storage. + + byte_filer index_file(c_index_held.observe(), "r"); + if (index_file.good()) { +#ifdef DEBUG_NECHUNG + LOG("index file exists"); +#endif + file_time index_time((FILE *)index_file.file_handle()); + file_time fortune_time((FILE *)fortune_file.file_handle()); + if (index_time >= fortune_time) { + // need to read in the list of indices + index_file.getline(buffer, MAX_LINE_LENGTH); + sscanf((char *)buffer.access(), "%d", &c_number_of_fortunes); +#ifdef DEBUG_NECHUNG + LOG(astring(astring::SPRINTF, "%d entries in index", + c_number_of_fortunes)); +#endif + return; + } + } + index_file.close(); + + // below is code for creating the list. + enum fortune_states { + chowing_separators, // looking for the breaks between fortunes. + adding_fortunes, // saw the separator so get ready for a new fortune. + chowing_fortunes, // currently in a fortune accumulating lines. + done_parsing // finished parsing the fortune file. + }; + + c_number_of_fortunes = 0; + fortune_states state = chowing_separators; + + int posn; + int_array fortune_posns; // our list of fortunes. + while (state != done_parsing) { +#ifdef DEBUG_NECHUNG + LOG(astring(astring::SPRINTF, "#%d", c_number_of_fortunes)); +#endif + if (fortune_file.eof()) { + // exit from the loop now... + state = done_parsing; + continue; + } + switch (state) { + case chowing_separators: { +#ifdef DEBUG_NECHUNG + LOG("chowseps, "); +#endif + posn = int(fortune_file.tell()); + if (posn < 0) + non_continuable_error(class_name(), func, "Cannot get file position."); + fortune_file.getline(buffer, MAX_LINE_LENGTH); +#ifdef DEBUG_NECHUNG + LOG(astring("got a line: ") + buffer); +#endif + if (buffer[0] != NECHUNG_SEPARATION_CHARACTER) state = adding_fortunes; + else { + // special casing is for when we see a separator on the line + // by itself versus when it is the beginning of a line. if the + // beginning of a line, we currently take that to mean the rest + // of the line is the fortune. + if (strlen((char *)buffer.access()) == 2) posn += 2; + else posn++; + state = adding_fortunes; + } + break; + } + case adding_fortunes: { +#ifdef DEBUG_NECHUNG + LOG("add forts, "); +#endif + fortune_posns += posn; + c_number_of_fortunes++; + state = chowing_fortunes; + break; + } + case chowing_fortunes: { +#ifdef DEBUG_NECHUNG + LOG("chow forts, "); +#endif + posn = int(fortune_file.tell()); + if (posn < 0) + non_continuable_error(class_name(), func, "Cannot get file size."); + fortune_file.getline(buffer, MAX_LINE_LENGTH); +#ifdef DEBUG_NECHUNG + LOG(astring(astring::SPRINTF, "got a line: %s", buffer.access())); + LOG(astring(astring::SPRINTF, "len is %d", strlen((char *)buffer.access()))); +#endif + if ( (buffer[0] == NECHUNG_SEPARATION_CHARACTER) + && (strlen((char *)buffer.access()) == 2) ) + state = chowing_separators; + else if (buffer[0] == NECHUNG_SEPARATION_CHARACTER) { + posn++; + state = adding_fortunes; + } + break; + } + case done_parsing: { + non_continuable_error(class_name(), func, "Illegal state reached."); + } + } + } + fortune_file.close(); + + // make a new index file. + index_file.open(c_index_held.observe(), "w"); + if (!index_file.good()) + non_continuable_error(class_name(), func, astring("Cannot open index file: ") + c_index_held); + astring to_write(astring::SPRINTF, "%d\n", c_number_of_fortunes); + index_file.write((abyte *)to_write.s(), to_write.length()); + for (int j = 0; j < c_number_of_fortunes; j++) { + to_write.sprintf("%d\n", fortune_posns[j]); + index_file.write((abyte *)to_write.s(), to_write.length()); + } + index_file.close(); +} + +astring nechung_oracle::pick_random() +{ + FUNCDEF("pick_random"); +#ifdef DEBUG_NECHUNG + LOG(astring("got to ") + func); +#endif + + byte_filer fortune_file(c_filename_held.s(), "rb"); + +///printf("num forts = %d\n", c_number_of_fortunes ); + + if (!fortune_file.good()) + non_continuable_error(class_name(), func, "Cannot open data file."); + int to_display = c_randomizer.inclusive(0, c_number_of_fortunes - 1); + +///printf("rand chose= %d\n", to_display); + +///// +///hmmm: this bit could be more efficient by just jumping to the Nth line +/// instead of reading through up to the Nth line. +///// + byte_filer index_file(c_index_held.observe(), "r"); + int chosen_posn = 0; // which position to read the chosen line at. + if (index_file.good()) { + astring accumulated_text; + byte_array buffer(MAX_LINE_LENGTH + 1); + for (int i = 0; i <= to_display; i++) { +#ifdef DEBUG_NECHUNG + accumulated_text += astring(astring::SPRINTF, "#%d: ", i); +#endif + index_file.getline(buffer, MAX_LINE_LENGTH); + sscanf((char *)buffer.access(), "%d", &chosen_posn); +#ifdef DEBUG_NECHUNG + accumulated_text += astring(astring::SPRINTF, "%d, ", chosen_posn); + if ((i + 1) % 5 == 0) accumulated_text += "\n"; +#endif + } +#ifdef DEBUG_NECHUNG + LOG(accumulated_text); +#endif + + } else { + non_continuable_error(class_name(), func, \ + astring("Could not open the index file \"") + c_index_held + "\""); + } + index_file.close(); +#ifdef DEBUG_NECHUNG + LOG(astring(astring::SPRINTF, "about to seek @ num %d and " + "index %d", to_display, chosen_posn)); +#endif + if (!fortune_file.seek(chosen_posn, byte_filer::FROM_START)) + non_continuable_error(class_name(), func, "Cannot seek to indexed position."); +#ifdef DEBUG_NECHUNG + LOG("after seek"); +#endif + + astring to_return; + byte_array temp(MAX_LINE_LENGTH + 1); + while (!fortune_file.eof()) { + int chars_read = fortune_file.getline(temp, MAX_LINE_LENGTH); + if (!chars_read) { + if (!fortune_file.eof()) { + non_continuable_error(class_name(), func, "Error while reading fortune."); + } else break; + } + if (temp[0] == NECHUNG_SEPARATION_CHARACTER) break; + else to_return += astring((char *)temp.access()); + } + return to_return; +} + +//hmmm: stolen from parser bits. reconnect when available. +bool is_eol(char to_check) +{ return (to_check == '\n') || (to_check == '\r'); } + +void nechung_oracle::display_random() +{ + astring to_show = pick_random(); + while (is_eol(to_show[to_show.end()])) + to_show.zap(to_show.end(), to_show.end()); + LOG(to_show); +} + diff --git a/nucleus/applications/nechung/nechung_oracle.h b/nucleus/applications/nechung/nechung_oracle.h new file mode 100644 index 00000000..31381b22 --- /dev/null +++ b/nucleus/applications/nechung/nechung_oracle.h @@ -0,0 +1,87 @@ +#ifndef NECHUNG_CLASS +#define NECHUNG_CLASS + +/*****************************************************************************\ +* * +* Name : nechung_oracle * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* This is the root nechung functionality. It provides a means of * +* randomly selecting an item out of a specially formatted file. If no index * +* file has previously been built for the file, then one is created. The * +* index file makes choosing a fortune randomly very quick; only a seek on * +* the much smaller index is needed in order to find the position of the * +* fortune to be shown. * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// Nechung works with a particular form of data file and will extract a random +// and hopefully auspicious message out of that file. An example of the file +// format is: +// msg1 +// ~ +// msg2 +// ~ +// (... more messages and tildes...) +// The tilde is the separation character mentioned below. + +#include +#include + +//#define DEBUG_NECHUNG + // uncomment this to get the debugging version. it is used by several files + // that are part of nechung. + +const char NECHUNG_SEPARATION_CHARACTER = '~'; + // this character separates the entries in the fortunes database. + +class nechung_oracle +{ +public: + nechung_oracle(const basis::astring &data_filename, const basis::astring &index_filename); + // the constructor needs the name of a nechung format data file in + // "data_filename" and the name of the index file to be used for that data + // file in "index_filename". + + virtual ~nechung_oracle(); + + DEFINE_CLASS_NAME("nechung_oracle"); + + basis::astring pick_random(); + // returns a randomly chosen fortune. + + void display_random(); + // selects an oracular pronouncement from the file and then shows it on + // standard output. + +private: + mathematics::chaos c_randomizer; // the random number generator we use. + basis::astring c_filename_held; // the data file's name. + basis::astring c_index_held; // the index file's name. + int c_number_of_fortunes; // how many fortunes exist in the file. + + void parse_file(); + // given the data file and index file, this will ensure that the index + // file is made up to date. it creates, if necessary, the file that + // contains the positions of fortunes in the data file. this is what + // we'll use to find the start of each fortune. if the file already + // exists, then it will just retrieve the number of fortunes from the index + // file. after this method, the pick_random() and display_random() methods + // are available. + + // disallowed. + nechung_oracle(const nechung_oracle &); + nechung_oracle &operator =(const nechung_oracle &); +}; + +#endif + diff --git a/nucleus/applications/nechung/readme b/nucleus/applications/nechung/readme new file mode 100644 index 00000000..ed2e1822 --- /dev/null +++ b/nucleus/applications/nechung/readme @@ -0,0 +1,7 @@ + +The Nechung oracle is a person manifesting the spirit of Dorje Drakden who is +the protector divinity of the Dalai Lama. + +This program cannot attempt to fulfil the same role as the Nechung oracle, but +hopefully it will give you good advice relating to your needs and your life. + diff --git a/nucleus/applications/nechung/test_nechung.sh b/nucleus/applications/nechung/test_nechung.sh new file mode 100644 index 00000000..3acabd95 --- /dev/null +++ b/nucleus/applications/nechung/test_nechung.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# prints out the time it takes to run the nechung oracle a thousand times. +# the number of seconds in the result should be equivalent to the number of +# milliseconds that nechung takes to run and produce a fortune, on average. + +export i=0 + +mdate +while [ $i -le 1000 ]; do +# echo $i + nechung >/dev/null + let i++ +done +mdate diff --git a/nucleus/applications/nechung/version.ini b/nucleus/applications/nechung/version.ini new file mode 100644 index 00000000..7b4eb044 --- /dev/null +++ b/nucleus/applications/nechung/version.ini @@ -0,0 +1,6 @@ +[version] +description=Nechung Oracle Program +root=nechung +name=Nechung +extension=exe + diff --git a/nucleus/applications/utilities/await_app_exit.cpp b/nucleus/applications/utilities/await_app_exit.cpp new file mode 100644 index 00000000..ec604ae4 --- /dev/null +++ b/nucleus/applications/utilities/await_app_exit.cpp @@ -0,0 +1,157 @@ +/* +* Name : await_app_exit +* Author : Chris Koeritz +* Purpose: * +* This program waits for a particular application to exit before this app * +* itself exits. This allows a pause while another possibly slow process is * +* leaving. * +* Copyright (c) 2003-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace filesystem; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; + +#undef BASE_LOG +#define BASE_LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) +#undef LOG +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +class await_app_exit : public application_shell +{ +public: + await_app_exit() : application_shell() {} + DEFINE_CLASS_NAME("await_app_exit"); + int execute(); +}; + +int await_app_exit::execute() +{ + FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + if (_global_argc < 3) { + BASE_LOG("This program needs two parameters on the command line. The first is an"); + BASE_LOG("application name (e.g. 'blofeld.exe' is a valid example--no path should be"); + BASE_LOG("included but the .exe suffix must be included) to seek out in the process"); + BASE_LOG("list and the second parameter is the time to wait for it to exit (in seconds)."); + BASE_LOG("This program will not exit until the specified application is no longer"); + BASE_LOG("running or the timeout elapses. If the timeout elapses, then a failure exit"); + BASE_LOG("will occur from this program so that it is known that the target application"); + BASE_LOG("never exited."); + return 2; + } + + astring app_name = _global_argv[1]; // get the app's name. + astring duration = _global_argv[2]; // get the time to wait. + int timeout = duration.convert(0) * 1000; + if (timeout < 0) { + LOG(astring("The timeout specified is invalid: ") + duration); + return 3; + } + + // now see if that app is even running. + process_control querier; + process_entry_array processes; + querier.query_processes(processes); + int_set pids; + time_stamp when_to_leave(timeout); // when we should stop checking. + + // wait for the app to go away. + while (querier.find_process_in_list(processes, app_name, pids)) { + // the program of interest is still running. + time_control::sleep_ms(100); + querier.query_processes(processes); + if (time_stamp() > when_to_leave) { + LOG(astring("The timeout elapsed and ") + app_name + " is still running."); + return 4; + } + } + LOG(astring("The ") + app_name + " process has exited."); + return 0; +} + +HOOPLE_MAIN(await_app_exit, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/applications/utilities/bytedump.cpp b/nucleus/applications/utilities/bytedump.cpp new file mode 100644 index 00000000..a30d16d1 --- /dev/null +++ b/nucleus/applications/utilities/bytedump.cpp @@ -0,0 +1,108 @@ +/*****************************************************************************\ +* * +* Name : dump_bytes * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Prints the files specified out in terms of their hex bytes. * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace filesystem; +using namespace structures; +using namespace textual; + +///HOOPLE_STARTUP_CODE; + +const int MAXIMUM_BYTEDUMP_BUFFER_SIZE = 32768; + // this is the size of the chunk we read from the files at a time. it is + // important to make this a multiple of 16, since that's the size of the line + // we use in the byte dumping. + +#define console program_wide_logger::get() + +int print_instructions_and_exit(char *program_name) +{ + console.log(astring(astring::SPRINTF, "\n\ +Usage:\n\t%s filename [filename]\n\n\ +Prints out (on standard output) a abyte dump of the files specified.\n\n", + program_name), ALWAYS_PRINT); + return 12; +} + +int main(int argc, char *argv[]) +{ + SETUP_CONSOLE_LOGGER; + + if (argc <= 1) return print_instructions_and_exit(argv[0]); + else { + int current_parameter = 0; +/* + if (argv[1][0] == '-') { + if (argv[1][1] == 't') { + current_parameter++; + open_file_as_text = true; + } else if (argv[1][1] == 'b') { + current_parameter++; + open_file_as_text = false; + } else print_instructions_and_exit(argv[0]); + } +*/ + bool past_first_file = false; + while (++current_parameter < argc) { + if (past_first_file) { + // we're into the second file so start using some white space. + console.log(astring::empty_string(), ALWAYS_PRINT); + console.log(astring::empty_string(), ALWAYS_PRINT); + } + past_first_file = true; // set condition for next time. + astring name = argv[current_parameter]; + byte_filer current(name, "rb"); + if (!current.good()) { + console.log(astring("Cannot find the file named \"") + name + astring("\"."), + ALWAYS_PRINT); + continue; + } + abyte buff[MAXIMUM_BYTEDUMP_BUFFER_SIZE + 10]; + // buffer plus some extra room. + bool printed_header = false; + int current_label = 0; + while (true) { + int bytes_read = current.read(buff, MAXIMUM_BYTEDUMP_BUFFER_SIZE); + if (bytes_read <= 0) break; // no contents. +//console.log(astring(astring::SPRINTF, "read %d bytes", bytes_read)); + if (!printed_header) { + console.log(name + ":", ALWAYS_PRINT); + console.log(astring::empty_string(), ALWAYS_PRINT); // blank line. + printed_header = true; + } + astring to_log = byte_formatter::text_dump(buff, bytes_read, + current_label); + if (to_log[to_log.end()] == '\n') + to_log.zap(to_log.end(), to_log.end()); + console.log(to_log, ALWAYS_PRINT); + current_label += bytes_read; + } + } + } + return 0; +} + diff --git a/nucleus/applications/utilities/checker.cpp b/nucleus/applications/utilities/checker.cpp new file mode 100644 index 00000000..c56cec3f --- /dev/null +++ b/nucleus/applications/utilities/checker.cpp @@ -0,0 +1,179 @@ +/*****************************************************************************\ +* * +* Name : checker * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Generates checksums for a set of files. * +* * +******************************************************************************* +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace basis; +using namespace structures; +using namespace timely; + +const int buffer_size = 4096; + +//HOOPLE_STARTUP_CODE; + +//#define DEBUG_CHECKER + // uncomment for noisy version. + +void print_instructions_and_exit(char *program_name) +{ + printf("\n\ +Usage:\n\t%s [-t] filename [filename]\n\n\ +This program generates a checksum for each file that is entered on the\n\ +command line. The checksum is (hopefully) an architecture independent\n\ +number that is a very compressed representation of the file gestalt.\n\ +If one compares two copies of a file, then the checksums should be identical.\n\ +This is a useful test of whether a file copy or a program download is\n\ +successful in making an identical version of the file. In particular, if the\n\ +file is made slightly bigger or smaller, or if an item in the file is changed,\n\ +then the checksums of the two versions should be different numbers.\n\n\ +The -b flag is used if the files are to be compared as binary files, and this\n\ +is also the default. The -t flag is used if the files are to be compared as\n\ +text files.\n", + program_name); + exit(1); +} + +#define HIGHEST_CHECK 32714 + +// do_checksum: takes the specified file name and generates a checksum for it. +// if the file is inaccessible or, at any point, reading it returns an +// error message, then a negative value is returned. +int do_checksum(char *file_name, int open_as_a_text_file) +{ + char file_open_mode[10]; + if (open_as_a_text_file) strcpy(file_open_mode, "rt"); + else strcpy(file_open_mode, "rb"); + FILE *opened_file = fopen(file_name, file_open_mode); +#ifdef DEBUG_CHECKER + LOG(astring("opened ") + file_name); +#endif + if (!opened_file) return common::NOT_FOUND; + int characters_read = 0; + int current_checksum_value = 0; + char buffer_chunk[buffer_size]; + while (!feof(opened_file)) { + characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size, + opened_file)); + // if result is 0 or negative, stop messing with the file. +#ifdef DEBUG_CHECKER + LOG(a_sprintf("char read = %d", characters_read)); +#endif + if (characters_read <= 0) { + if (characters_read < 0) current_checksum_value = -1; + else if (current_checksum_value == 0) current_checksum_value = -1; + break; + } + current_checksum_value = (current_checksum_value + + checksums::bizarre_checksum((abyte *)buffer_chunk, characters_read)) + % HIGHEST_CHECK; +#ifdef DEBUG_CHECKER + LOG(a_sprintf("current checksum=%d", current_checksum_value)); +#endif + } + fclose(opened_file); + return int(current_checksum_value); +} + +// do_fletcher_checksum: takes the specified file name and generates a fletcher +// checksum for it. if the file is inaccessible or, at any point, +// reading it returns an error message, then a negative value is returned. +int do_fletcher_checksum(char *file_name, int open_as_a_text_file) +{ + char file_open_mode[10]; + if (open_as_a_text_file) strcpy(file_open_mode, "rt"); + else strcpy(file_open_mode, "rb"); + FILE *opened_file = fopen(file_name, file_open_mode); +#ifdef DEBUG_CHECKER + LOG(astring("opened ") + file_name); +#endif + if (!opened_file) return common::NOT_FOUND; + int characters_read = 0; + int current_checksum_value = 0; + char buffer_chunk[buffer_size]; + while (!feof(opened_file)) { + characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size, + opened_file)); + // if result is 0 or negative, stop messing with the file. +#ifdef DEBUG_CHECKER + LOG(a_sprintf("char read = %d", characters_read)); +#endif + if (characters_read <= 0) { + if (characters_read < 0) current_checksum_value = -1; + else if (current_checksum_value == 0) current_checksum_value = -1; + break; + } + current_checksum_value = checksums::rolling_fletcher_checksum + ((uint16)current_checksum_value, (abyte *)buffer_chunk, + characters_read); +#ifdef DEBUG_CHECKER + LOG(a_sprintf("current checksum=%d", current_checksum_value)); +#endif + } + fclose(opened_file); + return current_checksum_value; +} + +int main(int argc, char *argv[]) +{ + char name[200]; + + // if the file is to be read as a text file, then this is true. + int open_file_as_text = false; + + if (argc <= 1) print_instructions_and_exit(argv[0]); + else { + int current_parameter = 0; + if (argv[1][0] == '-') { + if (argv[1][1] == 't') { + current_parameter++; + open_file_as_text = true; + } else if (argv[1][1] == 'b') { + current_parameter++; + open_file_as_text = false; + } else print_instructions_and_exit(argv[0]); + } + bool printed_header = false; + while (++current_parameter < argc) { + if (!printed_header) { + printed_header = true; + printf("%s\n", (astring("[ checker running at ") + time_stamp::notarize(true) + "]").s()); + printf("bizarro fletcher filename\n"); + printf("======= ======== ========\n"); + } + strcpy(name, argv[current_parameter]); + int checksum_of_file = do_checksum(name, open_file_as_text); + int fletcher_chksum = do_fletcher_checksum(name, open_file_as_text); + if (checksum_of_file >= 0) { + printf("%s", a_sprintf(" %05d 0x%04x %s\n", checksum_of_file, + fletcher_chksum, name).s()); + } else { + printf("%s", a_sprintf("%s is inaccessible.\n", name).s()); + } + } + } + return 0; +} + diff --git a/nucleus/applications/utilities/ini_edit.cpp b/nucleus/applications/utilities/ini_edit.cpp new file mode 100644 index 00000000..a56bf6a9 --- /dev/null +++ b/nucleus/applications/utilities/ini_edit.cpp @@ -0,0 +1,126 @@ +/*****************************************************************************\ +* * +* Name : ini_edit * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Provides command line ini editing capabilities. These include both * +* reading of ini files and writing new entries to them. * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; + +#undef LOG +#define LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) + +class ini_editor : public application_shell +{ +public: + ini_editor() + : application_shell(), + _app_name(filename(application::_global_argv[0]).basename()) {} + DEFINE_CLASS_NAME("ini_editor"); + virtual int execute(); + int print_instructions(); + +private: + astring _app_name; +}; + +int ini_editor::print_instructions() +{ + LOG(a_sprintf("\ +%s: This program needs five parameters to process an ini file.\n\ +There are two major operations, read and write. The type of operation\n\ +should be the first parameter. The other parameters are similar for both\n\ +operations, except for the last parameter. These are as follows:\n\ +Reading:\n\ +\tread inifile section entry defaultvalue\n\ + This reads the \"inifile\" specified and looks for the \"section\" and\n\ +\"entry\" name in the file. It will either return (via standard output)\n\ +the value found there or it will return the \"defaultvalue\". No error\n\ +will be raised if the entry is missing, but the default signals that no\n\ +value was defined.\n\ + Additionally, if the entry name is the special value \"whole_section\",\n\ +then the entire section will be read and returned as a CSV list. If the\n\ +section is empty, then the default string is returned instead.\n\ +Writing:\n\ +\twrite inifile section entry newvalue\n\ + This writes a new item with contents \"newvalue\" into the \"inifile\"\n\ +in the \"section\" at the \"entry\" specified. This should always succeed\n\ +unless the ini file is not writable (in which case an error should be\n\ +returned). Nothing is send to standard output for a write operation.\n\ +", _app_name.s())); + return 23; +} + +int ini_editor::execute() +{ + SETUP_CONSOLE_LOGGER; + + if (application::_global_argc < 6) return print_instructions(); + + astring operation = application::_global_argv[1]; + bool read_op = true; + if ( (operation[0] == 'w') || (operation[0] == 'W') ) read_op = false; + astring ini_file = application::_global_argv[2]; + astring section = application::_global_argv[3]; + astring entry = application::_global_argv[4]; + astring value = application::_global_argv[5]; + ini_configurator ini(ini_file, ini_configurator::RETURN_ONLY); + if (read_op) { + // read the entry from the ini file. + astring found; + if (entry.equal_to("whole_section")) { + // they want the whole section back at them. + string_table found; + bool worked = ini.get_section(section, found); + if (!worked) program_wide_logger::get().log(value, ALWAYS_PRINT); // default. + else { + // generate answer as csv. + astring temp; + list_parsing::create_csv_line(found, temp); + program_wide_logger::get().log(temp, ALWAYS_PRINT); // real value read. + } + } else { + bool worked = ini.get(section, entry, found); +/// program_wide_logger::get().eol(log_base::NO_ENDING); + if (!worked) program_wide_logger::get().log(value, ALWAYS_PRINT); // default. + else program_wide_logger::get().log(found, ALWAYS_PRINT); // real value read. + } + } else { + // write the entry to the ini file. + bool worked = ini.put(section, entry, value); + if (!worked) exit(28); // failure to write the entry. + } + + return 0; +} + +HOOPLE_MAIN(ini_editor, ) + diff --git a/nucleus/applications/utilities/makefile b/nucleus/applications/utilities/makefile new file mode 100644 index 00000000..2ba9d0eb --- /dev/null +++ b/nucleus/applications/utilities/makefile @@ -0,0 +1,19 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +PROJECT = utilities +TYPE = application +ifeq "$(OMIT_VERSIONS)" "" + SOURCE += util_version.rc +endif +LOCAL_LIBS_USED = application loggers mathematics processes textual timely configuration filesystem structures basis +TARGETS = await_app_exit.exe bytedump.exe checker.exe ini_edit.exe mdate.exe splitter.exe +#check_address.exe check_eol.exe create_guid.exe cstrip.exe \ + empty.exe find_non_ascii.exe find_text.exe insert_before.exe \ + lexcaser.exe machine_name.exe merge_inis.exe net_calc.exe pi.exe \ + replace_text.exe show_args.exe show_versions.exe to_lower.exe uucat.exe +#VCPP_USE_SOCK=t + +include cpp/rules.def + diff --git a/nucleus/applications/utilities/mdate.cpp b/nucleus/applications/utilities/mdate.cpp new file mode 100644 index 00000000..ffb1a662 --- /dev/null +++ b/nucleus/applications/utilities/mdate.cpp @@ -0,0 +1,45 @@ +/*****************************************************************************\ +* * +* Name : mdate * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Provides a more informative date command, providing milliseconds also. * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace structures; +using namespace timely; + +class mdate_app : public application_shell +{ +public: + virtual int execute() { + SETUP_CONSOLE_LOGGER; + program_wide_logger::get().log(time_stamp::notarize(false), ALWAYS_PRINT); + return 0; + } + DEFINE_CLASS_NAME("mdate_app"); +}; + +HOOPLE_MAIN(mdate_app, ) + diff --git a/nucleus/applications/utilities/splitter.cpp b/nucleus/applications/utilities/splitter.cpp new file mode 100644 index 00000000..0de8347a --- /dev/null +++ b/nucleus/applications/utilities/splitter.cpp @@ -0,0 +1,141 @@ +/*****************************************************************************\ +* * +* Name : splitter * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Takes text as input and splits the lines so that they will fit on a * +* standard 80 column terminal. * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; + +const int MAX_BUFFER = 1024; + +class splitter_app : public application_shell +{ +public: + splitter_app() : application_shell() {} + + DEFINE_CLASS_NAME("splitter_app"); + + virtual int execute(); + + int print_instructions(); + +private: +}; + +////////////// + +int splitter_app::print_instructions() +{ + astring name = filename(_global_argv[0]).basename().raw(); + log(a_sprintf("%s usage:", name.s())); + log(astring::empty_string()); + log(a_sprintf("\ +This program splits long lines in input files into a more reasonable size.\n\ +Any filenames on the command line are split and sent to standard output.\n\ +The following options change how the splitting is performed:\n\ + --help or -?\tShow this help information.\n\ + --mincol N\tMinimum column to use for output.\n\ + --maxcol N\tMinimum column to use for output.\n\ +")); + return -3; +} + +int splitter_app::execute() +{ + command_line cmds(_global_argc, _global_argv); // parse the command line up. + + // retrieve any specific flags first. + astring temp; + int min_col = 0; + if (cmds.get_value("mincol", temp)) + min_col = temp.convert(min_col); + int max_col = 77; + if (cmds.get_value("maxcol", temp)) + max_col = temp.convert(max_col); + // look for help command. + int junk_index = 0; + if (cmds.find("help", junk_index, false) + || cmds.find('h', junk_index, false) + || cmds.find("?", junk_index, false) + || cmds.find('?', junk_index, false) ) { + print_instructions(); + return 0; + } + + // gather extra input files. + string_set input_files; + for (int i = 0; i < cmds.entries(); i++) { + const command_parameter &curr = cmds.get(i); + if (curr.type() == command_parameter::VALUE) { +//log(astring("adding input file:") + curr.text()); + input_files += curr.text(); + } + } + + astring accumulator; + for (int q = 0; q < input_files.length(); q++) { + byte_filer current(input_files[q], "r"); + if (!current.good()) continue; + while (!current.eof()) { + astring line_read; + int num_chars = current.getline(line_read, MAX_BUFFER); + if (!num_chars) continue; +//printf("line len=%d, cont=%s\n", line_read.length(), line_read.s()); + accumulator += line_read; +//// accumulator += '\n'; + } + } + + // now get from standard input if there weren't any files specified. + if (!input_files.length()) { + char input_line[MAX_BUFFER + 2]; + while (!feof(stdin)) { + char *got = fgets(input_line, MAX_BUFFER, stdin); + if (!got) break; +//printf("line=%s\n", got); + accumulator += got; +//// accumulator += '\n'; + } + } +//printf("splitting accum with %d chars...\n", accumulator.length()); + astring chewed; + string_manipulation::split_lines(accumulator, chewed, min_col, max_col); +//printf("chewed string now has %d chars...\n", chewed.length()); + printf("%s", chewed.s()); + return 0; +} + +////////////// + +HOOPLE_MAIN(splitter_app, ) + diff --git a/nucleus/applications/utilities/splitter_test_1.txt b/nucleus/applications/utilities/splitter_test_1.txt new file mode 100644 index 00000000..f1a009ef --- /dev/null +++ b/nucleus/applications/utilities/splitter_test_1.txt @@ -0,0 +1,33 @@ + +FROOP Corporation + +Software Design Description (SDD) + +Document: Outcome and Diagnostic Standardization Proposal + +Project: Snoopter 5.1 + +Author: Chris Koeritz + +Created: 11/17/2004 + +Revision: 2 + +The Problem + + 1. The Snoopter product incorporates hundreds of C++ classes. Many of these define a set of outcomes that represent the exit status of a function invocation. This is a useful feature, rather than just returning a boolean result, but it has spread like wildfire and the set of outcomes today is very large. One part of the problem is that it is not clear to our customers what the outcomes even are, much less what they mean. + + 2. This impacts the logging of diagnostic information too. Sometimes the bare numerical values of outcomes are logged, whereas other times the outcome name is logged. While logging the names of outcomes is the preferred method, even then the set of names and what they mean is not necessarily clear. Another part of the problem is thus that diagnostic entries using outcomes are more difficult to decode than necessary. + + 3. Beyond the potential confusion that can arise from logging outcomes, some of our diagnostic entries are unclear or incomplete. The entries describe a problem, but sometimes they don't describe the object that the problem pertains to or the don't provide enough information to really understand what the problem is. Since each programmer writes their own log entries according to their own predilections, the diagnostic traces from Snoopter can be quirky and differ substantially from program to program. Some of this is unavoidable, but we need to provide better and more complete logging. + + 4. + +The Solution + + 1. One aspect of this project is to define a very low-level set of outcomes that cover most common function results. These will be used wherever possible as the outcomes returned by our C++ classes. There may still need to be a larger set of outcomes for certain classes, since their behavior may not make sense to describe at a very low-level. To support these extensions to the low-level set of outcomes, there needs to be a set of tools for adding new outcomes programmatically and in a way that is stable (i.e., the set does not change between releases unless intentionally modified) and verifiable (i.e., ensuring that no two outcomes share the same numeric value unless they are the same outcome). + + 2. It is important for the customer's sanity (and our own) that we have a way to produce a list of all outcomes in the program, their numerical values, and some description of what that outcome means. Part of the standardization project is to produce a tool capable of listing all of the Snoopter outcomes in just this way. + + 3. This project will create a set of guidelines for how to log the right amount of information when describing a situation in a log file. This will not be something that can be absolute and unvarying for everyone, but having a set of clear techniques for creating good log entries will be valuable. + diff --git a/nucleus/applications/utilities/splitter_test_2.txt b/nucleus/applications/utilities/splitter_test_2.txt new file mode 100644 index 00000000..a20779ce --- /dev/null +++ b/nucleus/applications/utilities/splitter_test_2.txt @@ -0,0 +1,8 @@ + +this file used to be done wrong; the snow lion tags would be at the end of the line at the bottom +but they should have been auto-ejected to the next line. if it looks kind of right now, then the problem is gone. + + +Karma has four main characteristics. The first is its increasing effect: goodness heralds further goodness and evil heralds further evil. Secondly, karma is definite: in the long run, goodness always produces joy and negativity always produces suffering. Thirdly, one never experiences a joy or sorrow that does not have an according karmic cause. And lastly, the karmic seeds that are placed on the mind at the time of an action will never lose their potency even in a hundred million lifetimes, but will lie dormant within the mind until one day the conditions that activate them appear. + +-- His Holiness the Dalai Lama from The Path to Enlightenment, published by Snow Lion Publications diff --git a/nucleus/applications/utilities/version.ini b/nucleus/applications/utilities/version.ini new file mode 100644 index 00000000..f1a352ea --- /dev/null +++ b/nucleus/applications/utilities/version.ini @@ -0,0 +1,6 @@ +[version] +description = Small Utility Programs +root = util +name = Utility App +extension = exe + diff --git a/nucleus/library/algorithms/makefile b/nucleus/library/algorithms/makefile new file mode 100644 index 00000000..8cf4b298 --- /dev/null +++ b/nucleus/library/algorithms/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = algorithms +TYPE = library +SOURCE = placeholder.cpp +TARGETS = algorithms.lib + +include cpp/rules.def + diff --git a/nucleus/library/algorithms/placeholder.cpp b/nucleus/library/algorithms/placeholder.cpp new file mode 100644 index 00000000..4878eca4 --- /dev/null +++ b/nucleus/library/algorithms/placeholder.cpp @@ -0,0 +1,4 @@ + + +// sole purpose of this is to make the lib be generated, +// even though we currently do not have any code that lives inside it. diff --git a/nucleus/library/algorithms/shell_sort.h b/nucleus/library/algorithms/shell_sort.h new file mode 100644 index 00000000..7e9b2539 --- /dev/null +++ b/nucleus/library/algorithms/shell_sort.h @@ -0,0 +1,65 @@ +#ifndef SHELL_SORT_CLASS +#define SHELL_SORT_CLASS + +////////////// +// Name : shell_sort +// Author : Chris Koeritz +////////////// +// Copyright (c) 1991-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +namespace algorithms { + +//! Orders an array in O(n log n) time. +/*! + This algorithm is based on Kernighan and Ritchie's "The C Programming Language". +*/ + +template +void shell_sort(type v[], int n, bool reverse = false) + //!< this function sorts a C array of the "type" with "n" elements. + /*!< the "type" of object must support comparison operators. if "reverse" + is true, then the list is sorted highest to lowest. */ +{ + type temp; + int gap, i, j; + // the gap sizes decrease quadratically(?). they partition the array of + // items that need to be sorted into first two groups, then four, then + // eight, .... + // within each gap's worth of the array, the next loop takes effect... + for (gap = n / 2; gap > 0; gap /= 2) { + // the i indexed loop is the base for where the comparisons are made in + // the j indexed loop. it makes sure that each item past the edge of + // the gap sized partition gets considered. + for (i = gap; i < n; i++) { + // the j indexed loop looks at the values in our current gap and ensures + // that they are in sorted order. + if (!reverse) { + // normal ordering. + for (j = i - gap; j >= 0 && v[j] > v[j + gap]; j = j - gap) { + // swap the elements that are disordered. + temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; + } + } else { + // reversed ordering. + for (j = i - gap; j >= 0 && v[j] < v[j + gap]; j = j - gap) { + // swap the elements that are disordered. + temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; + } + } + } + } +} + +} // namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/application_shell.cpp b/nucleus/library/application/application_shell.cpp new file mode 100644 index 00000000..a894bfac --- /dev/null +++ b/nucleus/library/application/application_shell.cpp @@ -0,0 +1,90 @@ +/*****************************************************************************\ +* * +* Name : application_shell * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "application_shell.h" + +#include +#include +#include +#include +#include +#include + +#include +#ifdef __UNIX__ + #include + #include +#endif + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace mathematics; +using namespace textual; +using namespace timely; + +namespace application { + +const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; + // maximum command line that we'll deal with here. + +application_shell *not_so_hidden_pointer = NULL; // fodder for the single instance function. + +application_shell *application_shell::single_instance() { return not_so_hidden_pointer; } + +application_shell::application_shell() +: c_rando(), + c_exit_value(120) // if this is never changed from the default, that in itself is an error. +{ + // we create a combo logger for program-wide usage. + SETUP_COMBO_LOGGER; + not_so_hidden_pointer = this; // setup the single instance method. +} + +application_shell::~application_shell() +{ + if (not_so_hidden_pointer == this) not_so_hidden_pointer = NULL; // only scorch it when it's us. +} + +outcome application_shell::log(const base_string &to_print, int filter) +{ + outcome to_return = common::OKAY; + if (program_wide_logger::get().member(filter)) { + astring temp_log(to_print); + if (temp_log.length()) + temp_log.insert(0, time_stamp::notarize(true)); + to_return = program_wide_logger::get().log(temp_log, filter); + } + return to_return; +} + +int application_shell::execute_application() +{ + try { + c_exit_value = execute(); + } catch (const char *message) { + printf("caught exception:\n%s\n", message); + } catch (astring message) { + printf("caught exception:\n%s\n", message.s()); + } catch (...) { + printf("caught exception: unknown type!\n"); + } + return c_exit_value; +} + +} //namespace. + diff --git a/nucleus/library/application/application_shell.h b/nucleus/library/application/application_shell.h new file mode 100644 index 00000000..950eb919 --- /dev/null +++ b/nucleus/library/application/application_shell.h @@ -0,0 +1,101 @@ +#ifndef APPLICATION_SHELL_CLASS +#define APPLICATION_SHELL_CLASS + +/*****************************************************************************\ +* * +* Name : application_shell * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "base_application.h" + +#include +#include + +namespace application { + +//! The application_shell is a base object for console programs. +/*! + It is generally used in that context (console mode), but can be employed as the root of windowed + programs also. The application_shell provides a few features, such as logging functionality, + that make it a bit easier than starting up a program from scratch every time. +*/ + +class application_shell : public base_application +{ +public: + application_shell(); + //!< constructs an application_shell to serve as the root of the program. + + virtual ~application_shell(); + + DEFINE_CLASS_NAME("application_shell") + + static application_shell *single_instance(); + //!< in a program with a single application_shell extant, this gives out the instance. + /*!< if there are more than one application_shells floating around a program, then this + will only give out the most recently registered. note that this pointer is not the + slightest bit thread safe if it's changing after the application shell has been constructed, + and that it cannot be relied upon until that's happened either. be careful. do not use + it in any function that might be invoked during program shutdown. */ + + virtual int execute_application(); + //!< runs the base class's execute() method and catches any exceptions due to it. + /*!< you can override this method, but generally should never need to. the derived class's + method should catch exceptions and deal with them meaningfully also. */ + + int exit_value() const { return c_exit_value; } + //!< once the application has finished executing, this will contain the exit value. + + const mathematics::chaos &randomizer() const { return c_rando; } + //!< provides access to the random number generator owned by this app. + +// static basis::astring application_name(); + //!< returns the full name of the current application. + +// static basis::u_int process_id(); + //!< returns the process id for this task, if that's relevant on the OS. + +// static basis::astring current_directory(); + //!< returns the current directory as reported by the operating system. + + virtual basis::outcome log(const basis::base_string &to_print, int filter = basis::ALWAYS_PRINT); + //!< as above, logs a line "to_print" but only if the "filter" is enabled. + /*!< the second version uses the filter value to assess whether to print + the string or not. the string will not print if that filter is not + enabled for the program wide logger. */ + +#ifdef __UNIX__ +// static basis::astring get_cmdline_from_proc(); + //!< retrieves the command line from the /proc hierarchy on linux. +// static basis::astring query_for_process_info(); + //!< seeks out process info for a particular process. +#endif + +protected: + virtual int execute() = 0; + //!< forwards base_application responsibility upwards to derived objects. + /*!< this should not be invoked by anyone in general; it is automatically called from + the constructor of this class. */ + +private: + mathematics::chaos c_rando; //!< random number generator. + int c_exit_value; //!< how did things end up for the app? + + // not applicable. + application_shell(const application_shell &); + application_shell &operator =(const application_shell &); +}; + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/base_application.h b/nucleus/library/application/base_application.h new file mode 100644 index 00000000..23a658a2 --- /dev/null +++ b/nucleus/library/application/base_application.h @@ -0,0 +1,67 @@ +#ifndef BASE_APPLICATION_CLASS +#define BASE_APPLICATION_CLASS + +////////////// +// Name : base_application +// Author : Chris Koeritz +////////////// +// Copyright (c) 2000-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include +#include + +namespace application { + +//! Provides a base object for the root application portion of a program. +/*! + This mainly defines an entry point into the application's real functionality. + Derived versions of the base_application can layer in more functionality as + appropriate for different types of applications. +*/ + +class base_application : public virtual basis::nameable +{ +public: + virtual const char *class_name() const = 0; // must be provided by implementor. + + virtual int execute() = 0; + //!< performs the main activity of this particular application object. + /*!< the method must be overridden by the derived object. a return value + for the program as a whole should be returned. */ +}; + +////////////// + +#if 0 + +//! This is an example usage of the base_application class... +class example_application : public base_application +{ +public: + example_application() : base_application() {} + DEFINE_CLASS_NAME("example_application"); + int execute() { /* do stuff and return final exit value. */ } +}; + +//! This is a sample main application for a console mode program... +int __example__main(int argc, char *argv[]) +{ + example_application root_program; + return root_program.execute(); +} + +#endif // example guard. + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/build_configuration.h b/nucleus/library/application/build_configuration.h new file mode 100644 index 00000000..1ad87b6c --- /dev/null +++ b/nucleus/library/application/build_configuration.h @@ -0,0 +1,59 @@ +#ifndef BUILD_CONFIGURATION_GROUP +#define BUILD_CONFIGURATION_GROUP + +////////////// +// Name : build configuration +// Author : Chris Koeritz +////////////// +// Copyright (c) 1995-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +/*! @file build_configuration.h + @brief Contains definitions that control how libraries and programs are built. + + These definitions can be used for declaring classes and functions that need + to be called from a DLL or that need to be compiled into a DLL. The import + style is used to declare that the item is being pulled out of a DLL. The + export style is used to declare that the item is being included in a DLL for + use by other functions. +*/ + +#include + +// Here is an example of a dynamic library header to be used by a particular library +// (presumably the gurpta library): +#if 0 + #ifndef GURPTA_DYNAMIC_LIBRARY_HEADER + #define GURPTA_DYNAMIC_LIBRARY_HEADER + // define BUILD_GURPTA when you are creating the dynamic library and + // define DYNAMIC_HOOPLE when you are importing code from the dynamic library. + #include + #if defined(BUILD_GURPTA) + #define GURPTA_LIBRARY HOOPLE_DYNAMIC_EXPORT + #elif defined(DYNAMIC_HOOPLE) + #define GURPTA_LIBRARY HOOPLE_DYNAMIC_IMPORT + #else + #define GURPTA_LIBRARY + #endif + #endif // outer guard. +#endif + +#ifdef __WIN32__ + #define HOOPLE_DYNAMIC_EXPORT __declspec(dllexport) + #define HOOPLE_DYNAMIC_IMPORT __declspec(dllimport) +#else + // no known requirements for these tags, so set them to nothing. + #define HOOPLE_DYNAMIC_EXPORT + #define HOOPLE_DYNAMIC_IMPORT +#endif + +#endif // outer guard. + diff --git a/nucleus/library/application/callstack_tracker.cpp b/nucleus/library/application/callstack_tracker.cpp new file mode 100644 index 00000000..c9f43bab --- /dev/null +++ b/nucleus/library/application/callstack_tracker.cpp @@ -0,0 +1,272 @@ + + + +/*****************************************************************************\ +* * +* Name : callstack_tracker * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#ifdef ENABLE_CALLSTACK_TRACKING + +// note: this object cannot be constructed when the memory_checker is still +// tracking memory leaks. it must be disabled so that this object can +// construct without being tracked, which causes an infinite loop. the code +// in the basis extern support takes care of that for us. + +#include "callstack_tracker.h" + +#include +#include + +////#undef new +//is this right way to clean that out. + +const int MAX_STACK_DEPTH = 2000; + // beyond that many stack frames, we will simply refuse to add any more. + +const int MAX_TEXT_FIELD = 1024; + // the most space we allow the class, function, and file to take up. + +const char *emptiness_note = "Empty Stack\n"; + //!< what we show when the stack is empty. + +////////////// + +class callstack_records +{ +public: + frame_tracking_instance _records[MAX_STACK_DEPTH + 2]; // fudging room. +}; + +////////////// + +// our current depth gives us our position in the array. we define our +// stack as starting at element zero, which is a null stack entry and +// corresponds to a depth of zero also. then, when the stack depth is one, +// we actually have an element in place and it resides at index 1. this +// scheme allows us to have an update to the line number just do nothing when +// there is no current stack. + +callstack_tracker::callstack_tracker() +: _bt(new callstack_records), + _depth(0), + _frames_in(0), + _frames_out(0), + _highest(0), + _unusable(false) +{ +//printf("callstack ctor\n"); +} + +callstack_tracker::~callstack_tracker() +{ +//printf("!!!!!!!!!!!!!!!!!! callstack dtor in\n"); + _unusable = true; + WHACK(_bt); +//printf("!!!!!!!!!!!!!!!!!! callstack dtor out\n"); +} + +bool callstack_tracker::push_frame(const char *class_name, const char *func, + const char *file, int line) +{ +//printf("callstack pushframe depth=%d in\n", _depth); + if (_unusable) return false; + if (_depth >= MAX_STACK_DEPTH) { + // too many frames already. + printf("callstack_tracker::push_frame: past limit at class=%s func=%s " + "file=%s line=%d\n", class_name, func, file, line); + return false; + } + _depth++; + if (_depth > _highest) _highest = _depth; + _frames_in += 1; + _bt->_records[_depth].assign(class_name, func, file, line); +//printf("callstack pushframe depth=%d out\n", _depth); + return true; +} + +bool callstack_tracker::pop_frame() +{ +//printf("callstack popframe depth=%d in\n", _depth); + if (_unusable) return false; + if (_depth <= 0) { + // how inappropriate of them; we have no frames. + _depth = 0; // we don't lose anything useful by forcing it to be zero. + printf("callstack_tracker::pop_frame stack underflow!\n"); + return false; + } + _bt->_records[_depth].clean(); + _depth--; + _frames_out += 1; +//printf("callstack popframe depth=%d out\n", _depth); + return true; +} + +bool callstack_tracker::update_line(int line) +{ + if (_unusable) return false; + if (!_depth) return false; // not as serious, but pretty weird. + _bt->_records[_depth]._line = line; + return true; +} + +char *callstack_tracker::full_trace() const +{ + if (_unusable) return strdup(""); +//printf("fulltrace in\n"); + char *to_return = (char *)malloc(full_trace_size()); + to_return[0] = '\0'; + if (!_depth) { + strcat(to_return, emptiness_note); + return to_return; + } + const int initial_len = MAX_TEXT_FIELD + 8; + char temp[initial_len]; + int allowed_len = initial_len; + // space provided for one text line. + // start at top most active frame and go down towards bottom most. + for (int i = _depth; i >= 1; i--) { + strcat(to_return, "\t"); // we left space for this and \n at end. + temp[0] = '\0'; + int len_class = strlen(_bt->_records[i]._class); + int len_func = strlen(_bt->_records[i]._func); + if (allowed_len > len_class + len_func + 6) { + allowed_len -= len_class + len_func + 6; + sprintf(temp, "\"%s::%s\", ", _bt->_records[i]._class, + _bt->_records[i]._func); + strcat(to_return, temp); + } + + temp[0] = '\0'; + int len_file = strlen(_bt->_records[i]._file); + if (allowed_len > len_file + 4) { + allowed_len -= len_file + 4; + sprintf(temp, "\"%s\", ", _bt->_records[i]._file); + strcat(to_return, temp); + } + + temp[0] = '\0'; + sprintf(temp, "\"line=%d\"", _bt->_records[i]._line); + int len_line = strlen(temp); + if (allowed_len > len_line) { + allowed_len -= len_line; + strcat(to_return, temp); + } + + strcat(to_return, "\n"); // we left space for this already. + } + +//printf("fulltrace out\n"); + return to_return; +} + +int callstack_tracker::full_trace_size() const +{ + if (_unusable) return 0; + if (!_depth) return strlen(emptiness_note) + 14; // liberal allocation. + int to_return = 28; // another hollywood style excess. + for (int i = _depth; i >= 1; i--) { + int this_line = 0; // add up parts for just this item. + + // all of these additions are completely dependent on how it's done above. + + int len_class = strlen(_bt->_records[i]._class); + int len_func = strlen(_bt->_records[i]._func); + this_line += len_class + len_func + 6; + + int len_file = strlen(_bt->_records[i]._file); + this_line += len_file + 4; + + this_line += 32; // extra space for line number and such. + + // limit it like we did above; we will use the lesser size value. + if (this_line < MAX_TEXT_FIELD + 8) to_return += this_line; + else to_return += MAX_TEXT_FIELD + 8; + } + return to_return; +} + +////////////// + +frame_tracking_instance::frame_tracking_instance(const char *class_name, + const char *func, const char *file, int line, bool add_frame) +: _frame_involved(add_frame), + _class(class_name? strdup(class_name) : NIL), + _func(func? strdup(func) : NIL), + _file(file? strdup(file) : NIL), + _line(line) +{ + if (_frame_involved) { +//printf("frametrackinst ctor in class=%s func=%s\n", class_name, func); + program_wide_stack_trace().push_frame(class_name, func, file, line); +//printf("frametrackinst ctor out\n"); + } +} + +frame_tracking_instance::frame_tracking_instance + (const frame_tracking_instance &to_copy) +: _frame_involved(false), // copies don't get a right to this. + _class(to_copy._class? strdup(to_copy._class) : NIL), + _func(to_copy._func? strdup(to_copy._func) : NIL), + _file(to_copy._file? strdup(to_copy._file) : NIL), + _line(to_copy._line) +{ +} + +frame_tracking_instance::~frame_tracking_instance() { clean(); } + +void frame_tracking_instance::clean() +{ + if (_frame_involved) { +//printf("frametrackinst clean\n"); + program_wide_stack_trace().pop_frame(); + } + _frame_involved = false; + free(_class); _class = NIL; + free(_func); _func = NIL; + free(_file); _file = NIL; + _line = 0; +} + +frame_tracking_instance &frame_tracking_instance::operator = + (const frame_tracking_instance &to_copy) +{ +//printf("frametrackinst tor = in\n"); + if (this == &to_copy) return *this; + assign(to_copy._class, to_copy._func, to_copy._file, to_copy._line); +//printf("frametrackinst tor = out\n"); + return *this; +} + +void frame_tracking_instance::assign(const char *class_name, const char *func, + const char *file, int line) +{ + clean(); + _frame_involved = false; // copies don't get a right to this. + _class = class_name? strdup(class_name) : NIL; + _func = func? strdup(func) : NIL; + _file = file? strdup(file) : NIL; + _line = line; +} + +void update_current_stack_frame_line_number(int line) +{ +//printf("frametrackinst updatelinenum in\n"); + program_wide_stack_trace().update_line(line); +//printf("frametrackinst updatelinenum out\n"); +} + +#endif // ENABLE_CALLSTACK_TRACKING + + + + diff --git a/nucleus/library/application/callstack_tracker.h b/nucleus/library/application/callstack_tracker.h new file mode 100644 index 00000000..bb75adaa --- /dev/null +++ b/nucleus/library/application/callstack_tracker.h @@ -0,0 +1,156 @@ +#ifndef CALLSTACK_TRACKER_CLASS +#define CALLSTACK_TRACKER_CLASS + +/*****************************************************************************\ +* * +* Name : callstack_tracker * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "definitions.h" + +#ifdef ENABLE_CALLSTACK_TRACKING + +#include "build_configuration.h" +#include "root_object.h" + +namespace application { + +// forward. +class callstack_records; +class callstack_tracker; + +////////////// + +callstack_tracker BASIS_EXTERN &program_wide_stack_trace(); + //!< a global object that can be used to track the runtime callstack. + +////////////// + +//! This object can provide a backtrace at runtime of the invoking methods. +/*! + The callstack tracking is hooked in through the FUNCDEF macros used to + set function names for logging. Thus it will only be visible if those + macros are used fairly carefully or if people invoke the stack frame addition + method themselves. +*/ + +class callstack_tracker +{ +public: + callstack_tracker(); + virtual ~callstack_tracker(); + + DEFINE_CLASS_NAME("callstack_tracker"); + + bool push_frame(const char *class_name, const char *func, const char *file, + int line); + //!< adds a new stack from for the "class_name" in "function" at the "line". + /*!< this function should be invoked when entering a new stack frame. the + "file" can be gotten from the __FILE__ macro and the "line" number can come + from __LINE__, but the "class_name" and "func" must be tracked some other + way. we recommend the FUNCDEF macro. this function might return false if + there is no longer any room for tracking more frames; that is a serious + issue that might indicate a runaway recursion or infinite loop. */ + + bool pop_frame(); + //!< removes the last callstack frame off from our tracking. + + bool update_line(int line); + //!< sets the line number within the current stack frame. + /*!< the current frame can reside across several line numbers, so this + allows the code to be more specific about the location of an invocation. */ + + char *full_trace() const; + //!< provides the current stack trace in a newly malloc'd string. + /*!< the user *must* free() the string returned. */ + + int full_trace_size() const; + //!< this returns the number of bytes needed for the above full_trace(). + + int depth() const { return _depth; } + //!< the current number of frames we know of. + + double frames_in() const { return _frames_in; } + //!< reports the number of call stack frames that were added, total. + + double frames_out() const { return _frames_out; } + //!< reports the number of call stack frames that were removed, total. + + double highest() const { return _highest; } + //!< reports the maximum stack depth seen during the runtime so far. + +private: + callstack_records *_bt; //!< the backtrace records for current program. + int _depth; //!< the current number of frames we know of. + double _frames_in; //!< number of frame additions. + double _frames_out; //!< number of frame removals. + double _highest; //!< the most number of frames in play at once. + bool _unusable; //!< object has already been destroyed. +}; + +////////////// + +//! a small object that represents a stack trace in progress. +/*! the object will automatically be destroyed when the containing scope +exits. this enables a users of the stack tracker to simply label their +function name and get the frame added. if they want finer grained tracking, +they should update the line number periodically through their function, +especially when memory is about to be allocated or where something might go +wrong. */ + +class frame_tracking_instance +{ +public: + // these are not encapsulated, but be careful with the contents. + bool _frame_involved; //!< has this object been added to the tracker? + char *_class, *_func, *_file; //!< newly allocated copies. + int _line; + + frame_tracking_instance(const char *class_name = "", const char *func = "", + const char *file = "", int line = 0, bool add_frame = false); + //!< as an automatic variable, this can hang onto frame information. + /*!< if "add_frame" is true, then this actually adds the stack frame in + question to the tracker. thus if you use this class at the top of your + function, such as via the FUNCDEF macro, then you can forget about having + to pop the frame later. */ + + frame_tracking_instance(const frame_tracking_instance &to_copy); + + ~frame_tracking_instance(); + //!< releases the information *and* this stack frame in the tracker. + + frame_tracking_instance &operator =(const frame_tracking_instance &to_copy); + + void assign(const char *class_name, const char *func, const char *file, + int line); + //!< similar to assignment operator but doesn't require an object. + + void clean(); + //!< throws out our accumulated memory and pops frame if applicable. +}; + +void update_current_stack_frame_line_number(int line); + //!< sets the line number for the current frame in the global stack trace. + +#else // ENABLE_CALLSTACK_TRACKING + // bogus replacements for most commonly used callstack tracking support. + #define frame_tracking_instance + #define __trail_of_function(p1, p2, p3, p4, p5) if (func) {} + // the above actually trades on the name of the object we'd normally + // define. it must match the object name in the FUNCDEF macro. + #define update_current_stack_frame_line_number(line) +#endif // ENABLE_CALLSTACK_TRACKING + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/command_line.cpp b/nucleus/library/application/command_line.cpp new file mode 100644 index 00000000..19f61810 --- /dev/null +++ b/nucleus/library/application/command_line.cpp @@ -0,0 +1,486 @@ +/*****************************************************************************\ +* * +* Name : command_line * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "command_line.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; + +namespace application { + +DEFINE_ARGC_AND_ARGV; + +command_parameter::command_parameter(parameter_types type) +: _type(type), _text(new astring) {} + +command_parameter::command_parameter(parameter_types type, const astring &text) +: _type(type), _text(new astring(text)) {} + +command_parameter::command_parameter(const command_parameter &to_copy) +: _type(VALUE), _text(new astring) +{ *this = to_copy; } + +command_parameter::~command_parameter() { WHACK(_text); } + +const astring &command_parameter::text() const { return *_text; } + +void command_parameter::text(const astring &new_text) { *_text = new_text; } + +command_parameter &command_parameter::operator = + (const command_parameter &to_copy) +{ + if (this == &to_copy) return *this; + _type = to_copy._type; + *_text = *to_copy._text; + return *this; +} + +////////////// + +// option_prefixes: the list of valid prefixes for options on a command line. +// these are the characters that precede command line arguments. For Unix, +// the default is a dash (-), while for DOS most programs use forward-slash +// (/). Adding more characters is trivial; just add a character to the list +// before the sentinel of '\0'. +#if defined(_MSC_VER) || defined(__MINGW32__) + static char option_prefixes[] = { '-', '/', '\0' }; +#elif defined(__UNIX__) + static char option_prefixes[] = { '-', '\0' }; +#else + #error "I don't know what kind of operating system this is." +#endif + +bool it_is_a_prefix_char(char to_test) +{ + for (int i = 0; option_prefixes[i]; i++) + if (to_test == option_prefixes[i]) return true; + return false; +} + +////////////// + +class internal_cmd_line_array_of_parms : public array {}; + +////////////// + +SAFE_STATIC_CONST(command_parameter, command_line::cmdline_blank_parm, ) + // our default return for erroneous indices. + +command_line::command_line(int argc, char *argv[]) +: _implementation(new internal_cmd_line_array_of_parms), + _program_name(new filename(directory::absolute_path(argv[0]))) +{ + argv++; // skip command name in argv. + + // loop over the rest of the fields and examine them. + string_array string_list; // accumulated below. + while (--argc > 0) { + astring to_store = argv[0]; // retrieve the current string. + string_list += to_store; // put the string in our list. + argv++; // next string. + } + parse_string_array(string_list); +} + +command_line::command_line(const astring &full_line) +: _implementation(new internal_cmd_line_array_of_parms), + _program_name(new filename) +{ + astring accumulator; + string_array string_list; + bool in_quote = false; +//hmmm: this is not quote right yet. +// use the separate command line method, but get it to run iteratively +// so we can keep pulling them apart? maybe it already does! +// separate is better because it handles escaped quotes. + for (int i = 0; i < full_line.length(); i++) { + char to_examine = full_line.get(i); + if (to_examine == '"') { + // it's a quote character, so maybe we can start eating spaces. + if (!in_quote) { + in_quote = true; + continue; // eat the quote character but change modes. + } + // nope, we're closing a quote. we assume that the quotes are + // around the whole argument. that's the best win32 can do at least. + in_quote = false; + to_examine = ' '; // trick parser into logging the accumulated string. + // intentional fall-through to space case. + } + + if (parser_bits::white_space(to_examine)) { + // if this is a white space, then we start a new string. + if (!in_quote && accumulator.t()) { + // only grab the accumulator if there are some contents. + string_list += accumulator; + accumulator = ""; + } else if (in_quote) { + // we're stuffing the spaces into the string since we're quoted. + accumulator += to_examine; + } + } else { + // not white space, so save it in the accumulator. + accumulator += to_examine; + } + } + if (accumulator.t()) string_list += accumulator; + // that partial string wasn't snarfed during the loop. + // grab the program name off the list so the parsing occurs as expected. + *_program_name = directory::absolute_path(string_list[0]); + string_list.zap(0, 0); + parse_string_array(string_list); +} + +command_line::~command_line() +{ + WHACK(_program_name); + WHACK(_implementation); +} + +int command_line::entries() const { return _implementation->length(); } + +filename command_line::program_name() const { return *_program_name; } + +const command_parameter &command_line::get(int field) const +{ + bounds_return(field, 0, entries() - 1, cmdline_blank_parm()); + return _implementation->get(field); +} + +void command_line::separate_command_line(const astring &cmd_line, + astring &app, astring &parms) +{ + char to_find = ' '; // the command separator. + if (cmd_line[0] == '\"') to_find = '\"'; + // if the first character is a quote, then we are seeing a quoted phrase + // and need to look for its completing quote. otherwise, we'll just look + // for the next space. + + int seek_posn = 1; // skip the first character. we have accounted for it. + // skim down the string, looking for the ending of the first phrase. + while (seek_posn < cmd_line.length()) { + // look for our parameter separator. this will signify the end of the + // first phrase / chunk. if we don't find it, then it should just mean + // there was only one item on the command line. + int indy = cmd_line.find(to_find, seek_posn); + if (negative(indy)) { + // yep, there wasn't a matching separator, so we think this is just + // one chunk--the app name. + app = cmd_line; + break; + } else { + // now that we know where our separator is, we need to find the right + // two parts (app and parms) based on the separator character in use. + if (to_find == '\"') { + // we are looking for a quote character to complete the app name. + if (cmd_line[indy - 1] == '\\') { + // we have a backslash escaping this quote! keep seeking. + seek_posn = indy + 1; + continue; + } + app = cmd_line.substring(0, indy); + parms = cmd_line.substring(indy + 2, cmd_line.end()); + // skip the quote and the obligatory space character after it. + break; + } else { + // simple space handling here; no escapes to worry about. + app = cmd_line.substring(0, indy - 1); + parms = cmd_line.substring(indy + 1, cmd_line.end()); + break; + } + } + } +} + +bool command_line::zap(int field) +{ + bounds_return(field, 0, entries() - 1, false); + _implementation->zap(field, field); + return true; +} + +// makes a complaint about a failure and sets the hidden commands to have a +// bogus entry so they aren't queried again. +#define COMPLAIN_CMDS(s) \ + listo_cmds += "unknown"; \ + COMPLAIN(s) + +string_array command_line::get_command_line() +{ +// FUNCDEF("get_command_line"); + string_array listo_cmds; + // the temporary string below can be given a flat formatting of the commands + // and it will be popped out into a list of arguments. + astring temporary; +#ifdef __UNIX__ + if (!_global_argc || !_global_argv) { + // our global parameters have not been set, so we must calculate them. + temporary = application_configuration::get_cmdline_from_proc(); + } else { + // we have easy access to command line arguments supposedly, so use them. + for (int i = 0; i < _global_argc; i++) { + // add a string entry for each argument. + listo_cmds += _global_argv[i]; + } + // we don't need a long string to be parsed; the list is ready. + return listo_cmds; + } +#elif defined(__WIN32__) + // we have easy access to the original list of commands. + for (int i = 0; i < _global_argc; i++) { + // add a string entry for each argument. + listo_cmds += _global_argv[i]; + } + return listo_cmds; +#else + COMPLAIN_CMDS("this OS doesn't support getting the command line."); + return listo_cmds; +#endif + + // now that we have our best guess at a flat representation of the command + // line arguments, we'll chop it up. + +//hmmm: this algorithm doesn't support spaces in filenames currently. +//hmmm: for windows, we can parse the quotes that should be around cmd name. +//hmmm: but for unix, the ps command doesn't support spaces either. how to +// get around that to support programs with spaces in the name? + int posn = 0; + int last_posn = -1; + while (posn < temporary.length()) { + posn = temporary.find(' ', posn); + if (non_negative(posn)) { + // found another space to turn into a portion of the command line. + listo_cmds += temporary.substring(last_posn + 1, posn - 1); + // grab the piece of string between the point just beyond where we + // last saw a space and the position just before the space. + last_posn = posn; // save the last space position. + posn++; // push the pointer past the space. + } else { + // no more spaces in the string. grab what we can from the last bit + // of the string that we see. + if (last_posn < temporary.length() - 1) { + // there's something worthwhile grabbing after the last place we + // saw a space. + listo_cmds += temporary.substring(last_posn + 1, + temporary.length() - 1); + } + break; // we're done finding spaces. + } + } + + return listo_cmds; +} + +astring command_line::text_form() const +{ + astring to_return; + const astring EOL = parser_bits::platform_eol_to_chars(); + for (int i = 0; i < entries(); i++) { + const command_parameter &curr = get(i); + to_return += a_sprintf("%d: ", i + 1); + switch (curr.type()) { + case command_parameter::CHAR_FLAG: + to_return += astring(" ") + curr.text() + EOL; + break; + case command_parameter::STRING_FLAG: + to_return += astring(" ") + curr.text() + EOL; + break; + case command_parameter::VALUE: // pass through to default. + default: + to_return += astring(" ") + curr.text() + EOL; + break; + } + } + return to_return; +} + +bool command_line::find(char option_character, int &index, + bool case_sense) const +{ + astring opt(option_character, 1); // convert to a string once here. + if (!case_sense) opt.to_lower(); // no case-sensitivity. + for (int i = index; i < entries(); i++) { +//hmmm: optimize this too. + if (get(i).type() == command_parameter::CHAR_FLAG) { + bool success = (!case_sense && get(i).text().iequals(opt)) + || (case_sense && (get(i).text() == opt)); + if (success) { + // the type is appropriate and the value is correct as well... + index = i; + return true; + } + } + } + return false; +} + +bool command_line::find(const astring &option_string, int &index, + bool case_sense) const +{ + FUNCDEF("find"); +if (option_string.length() && (option_string[0] == '-') ) +LOG(astring("found option string with dash! string is: ") + option_string); + + for (int i = index; i < entries(); i++) { + if (get(i).type() == command_parameter::STRING_FLAG) { + bool success = (!case_sense && get(i).text().iequals(option_string)) + || (case_sense && (get(i).text() == option_string)); + if (success) { + // the type is appropriate and the value is correct as well... + index = i; + return true; + } + } + } + return false; +} + +bool command_line::get_value(char option_character, astring &value, + bool case_sense) const +{ + value = ""; + int posn = 0; // where we find the flag. + if (!find(option_character, posn, case_sense)) return false; + + // get the value after the flag, if there is such. + posn++; // this is where we think our flag's value lives. + if (posn >= entries()) return false; + + // there's still an entry after where we found our flag; grab it. + command_parameter cp = get(posn); + if (cp.type() != command_parameter::VALUE) return false; + + // finally; we've found an appropriate text value. + value = cp.text(); + return true; +} + +bool command_line::get_value(const astring &option_string, astring &value, + bool case_sense) const +{ + FUNCDEF("get_value"); +if (option_string.length() && (option_string[0] == '-') ) +LOG(astring("found option string with dash! string is: ") + option_string); + + value = ""; + int posn = 0; // where we find the flag. + if (!find(option_string, posn, case_sense)) return false; + + // get the value after the flag, if there is such. + posn++; // this is where we think our flag's value lives. + if (posn >= entries()) return false; + + // there's still an entry after where we found our flag; grab it. + command_parameter cp = get(posn); + if (cp.type() != command_parameter::VALUE) return false; + + // finally; we've found an appropriate text value. + value = cp.text(); + return true; +} + +void command_line::parse_string_array(const string_array &to_parse) +{ + bool still_looking_for_flags = true; // goes to false when only values left. + // loop over the fields and examine them. + for (int i = 0; i < to_parse.length(); i++) { + // retrieve a character from the current string. + int index = 0; + char c = to_parse[i].get(index++); + // we check whether it's a prefix character, and if so, what kind. + if (still_looking_for_flags && it_is_a_prefix_char(c)) { + // at least one prefix is there, so treat this as a flag. + bool gnu_type_of_flag = false; + if (it_is_a_prefix_char(to_parse[i].get(index))) { + // there's a special GNU double flag beginner. + index++; // skip that extra one. + if ( (index >= to_parse[i].length()) + || parser_bits::white_space(to_parse[i].get(index))) { + // special case of '--' (or '//' i suppose) with white space or + // nothing else afterwards; indicates that the rest of the items + // should just be values, not flags. + still_looking_for_flags = false; + continue; // we ate that item. + } + gnu_type_of_flag = true; + } + // everything after the prefixes is considered part of the flag; they're + // either individual flag characters (on a single prefix) or they're the + // full name for the flag (gnu style). + c = 1; // reset to a true bool value. + astring gnu_accumulator; // if processing a gnu flag, it arrives here. + while (c) { + if (!gnu_type_of_flag) { + // add as many flag parameters as possible. + c = to_parse[i].get(index++); + // c will be zero once we hit the end of the string. + if (c) { + command_parameter to_add(command_parameter::CHAR_FLAG, astring(c, 1)); + *_implementation += to_add; + } + } else { + // the gnu flag name is added to here. + c = to_parse[i].get(index++); // zero at end of string. + if (c) + gnu_accumulator += c; // one more character. + } + } + if (gnu_accumulator.t()) { + // we've accumulated a gnu flag, so store it. + command_parameter to_add(command_parameter::STRING_FLAG, + gnu_accumulator); + *_implementation += to_add; + } + } else { + // add a value type of command_parameter. + astring found = to_parse[i]; + command_parameter to_add(command_parameter::VALUE, found); + *_implementation += to_add; + } + } +} + +astring command_line::gather(int &index) const +{ + astring to_return; + for (int i = index; i < entries(); i++) { + if (get(i).type() == command_parameter::CHAR_FLAG) { + index = i; + return to_return; + } else to_return += get(i).text(); + } + index = entries() - 1; + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/application/command_line.h b/nucleus/library/application/command_line.h new file mode 100644 index 00000000..989aac5d --- /dev/null +++ b/nucleus/library/application/command_line.h @@ -0,0 +1,219 @@ +#ifndef COMMAND_LINE_CLASS +#define COMMAND_LINE_CLASS + +/*****************************************************************************\ +* * +* Name : command_line * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace application { + +//! This class parses the command line passed to the main() function. +/*! + The main function might be called WinMain or be implemented by CWinApp in MS-Windows, but all + of these types of applications will still have flags passed to them. This class constructs a + list of parameters from the command line provided by the OS. A parameter is either a command + flag or a value string. Flag characters take the form of "-l" or "-Q" or "-als" (which has + three flags). Value strings are other strings found on the command line that do not start with + the separator character (usually '-'). Flag characters are all broken up into separate entries. + A special kind of flag uses a double separator to allow multiple character flag names (e.g., + the string "--follow" where the flag name is "follow"), as in GNU software. + This class knows about the convention of a paramter of just '--' being used to + indicate that the rest of the line is all normal parameters and has no intentional flags + in them. This allows passing of character strings that would otherwise be misinterpreted + as flags rather than literal input. + +(not implemented) + A special feature is provided to allow parsing of flag characters followed + directly by the value (as in "-fbroiler.h", where the flag character is 'f' + and the value is "broiler.h". +(not implemented) +*/ + +// forward declarations. +class internal_cmd_line_array_of_parms; + +class command_parameter : public virtual basis::root_object +{ +public: + enum parameter_types { VALUE, CHAR_FLAG, STRING_FLAG, BOGUS_ITEM }; + + command_parameter(parameter_types type = BOGUS_ITEM); + //!< default constructor initializes to mostly blank state. + + command_parameter(parameter_types type, const basis::astring &text); + //!< constructs a parameter of "type" where the value is "text". + /*!< if the "type" is CHAR_FLAG, then this should be a string of + one character. for STRING_FLAG, the length is arbitrary. */ + + command_parameter(const command_parameter &to_copy); + + ~command_parameter(); + + DEFINE_CLASS_NAME("command_parameter"); + + command_parameter &operator =(const command_parameter &to_copy); + + parameter_types type() const { return _type; } + //!< observes the type of the parameter. + void type(parameter_types new_type) { _type = new_type; } + //!< modifies the type of the parameter. + + const basis::astring &text() const; + //!< observes the string contents. + void text(const basis::astring &new_text); + //!< modifies the string contents. + +private: + parameter_types _type; + basis::astring *_text; +}; + +////////////// + +class command_line +{ +public: + command_line(int argc, char *argv[]); + //!< takes command line parameters in the form of "argc" and "argv". + /*!< this is suitable for most C++ main programs. the first "argv" string (element zero) is + ignored because it is assumed that it is the program name. that means that the array of + command parameters here will be (argc - 1) in length, and that index zero of our array has + the first "real" parameter that was passed to the program (i.e., not it's name). + note that the unaltered command parameters of argc and argv become available in the global + variables _global_argc and _global_argv. */ + + command_line(const basis::astring &to_parse); + //!< takes a string form of the command line. + /*!< this is the form rendered by GetCommandLine() in Win32. on certain + win32 platforms, this may not return a full path for the program_name() + method. this uses the separate_command_line() method to pick out the + relevant pieces and supports embedded, escaped quotes. */ + + virtual ~command_line(); + + DEFINE_CLASS_NAME("command_line"); + + filesystem::filename program_name() const; + //!< Returns the program name found in the command line. + + static void separate_command_line(const basis::astring &cmd_line, basis::astring &app, + basis::astring &parms); + //!< breaks apart a command line in "cmd_line" into "app" and "parms". + /*!< when given a full command line, where the application to run is the + first chunk and its parameters (if any) are subsequent chunks, this will + store the application name in "app" and the rest of the parameters in + "parms". this expects any paths in the "cmd_line" that contain spaces + to be surrounded by quotes. if there are any quote characters that are + escaped, they are considered to be embedded in the parameter string; they + will not be considered as matching any pending closing quotes. */ + + int entries() const; + //!< Returns the number of fields found on the command line. + /*!< This does not include the program name found; that's only + accessible through the program_name() method. */ + + const command_parameter &get(int field) const; + //!< Returns the parameter at the "field" specified. + /*!< The "field" ranges from zero through "entries() - 1" inclusive. if + an invalid index is used, then the type will be BOGUS_ITEM. */ + + bool zap(int field); + //!< eats the entry at position "field". + /*!< this is useful for cleaning out entries that have already been dealt + with. */ + + // note: in the following, if "case_sense" is true, then the searches are + // case-sensitive. otherwise, case of the flags is not a concern. + // the returned values always retain the original case. + + bool find(char option_character, int &index, bool case_sense = true) const; + //!< Returns true if the "option_character" is found in the parameters. + /*!< The search starts at the "index" specified, and if the item is found, + its location is returned in "index" and the function returns true. + Otherwise false is returned and the "index" is not modified. */ + bool find(const basis::astring &option_string, int &index, + bool case_sense = true) const; + //!< Returns true if the "option_string" is found in the parameters. + + bool get_value(char option_character, basis::astring &value, + bool case_sense = true) const; + //!< retrieves the "value" found for the option flag specified. + /*!< this is useful for command lines with standard spacing. for example, + if the command line is "-Q query.bop --Linkage plorgs.txt", then this + function would return "query.bop" for a search on 'Q' and the find() + method below would return "plorgs.txt" for the string flag search on + "Linkage". */ + bool get_value(const basis::astring &option_string, basis::astring &value, + bool case_sense = true) const; + //!< retrieves the "value" found for the "option_string" specified. + +//is this useful? it's kind of like what we need for special flags (like +// -fgob.h, where gob.h is a value parameter) but needs to terminate +//differently for that to work. + basis::astring gather(int &index) const; + //!< coalesces parameters together until the next option flag. + /*!< Returns a string constructed from the concatenation of the strings + for the parameters at all indices in the list starting at "index" until + an option character is found. Note that this means an empty string + will be returned if the parameter at "index" has an option character, + or if "index" is greater than or equal to "elements()". + After gather, "index" is set to the last location included in the + string. "index" is set to the last index in the list if "index" was + past the end to begin with or if strings are gathered up to the last + index. otherwise, "index" is unchanged if nothing was gathered. */ + + basis::astring text_form() const; + //!< returns a string with all the information we have for the command line. + + static structures::string_array get_command_line(); + //!< returns the command line passed to the program as a list of strings. + /*!< the string at index zero is the program name. this is just a useful + helper function and is not normally needed by users of the command_line + object. */ + +private: + internal_cmd_line_array_of_parms *_implementation; //!< held parameters. + filesystem::filename *_program_name; //!< the name of this program. + + void parse_string_array(const structures::string_array &to_parse); + //!< pulls all the strings in "to_parse" into the command_parameter list. + + // forbidden: + command_line(const command_line &to_copy); + command_line &operator =(const command_line &to_copy); + + static const command_parameter &cmdline_blank_parm(); +}; + +////////////// + +// this declares a program-wide command-line argument storage area. + +extern int _global_argc; +extern char **_global_argv; +//! this macro allocates space for the command-line storage areas. +#define DEFINE_ARGC_AND_ARGV int _global_argc = 0; char **_global_argv = NIL + +//! this macro assigns our command-line parameters for this program. +#define SET_ARGC_ARGV(argc, argv) { \ + application::_global_argc = argc; \ + application::_global_argv = argv; \ +} + +} //namespace. + +#endif + diff --git a/nucleus/library/application/dll_root.cpp b/nucleus/library/application/dll_root.cpp new file mode 100644 index 00000000..7eab74b3 --- /dev/null +++ b/nucleus/library/application/dll_root.cpp @@ -0,0 +1,136 @@ +/*****************************************************************************\ +* * +* Name : DLL Main Root Support * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// Thanks to Andy Tan for some research into MFC extension dlls. + +#include +#include +#include + +//HOOPLE_STARTUP_CODE_DLL; + // initialize objects needed by the hoople libs. + +#ifdef _AFXDLL + +#ifdef DEBUG + #define TRACE_PRINTER(s) TRACE_PRINT(s) +#else + #define TRACE_PRINTER(s) +#endif + +// base for AFX dlls. + +#include // MFC extensions. +#include // Extension DLL declarations; include only once! + +bool check_DLL_versions(); + // supplied by the library's version of dllmain.cpp. + +static AFX_EXTENSION_MODULE SomeDLL = { NULL, NULL }; + +#ifndef DLL_NAME + #define DLL_NAME "unnamed DLL" +#endif + +extern "C" int APIENTRY +DllMain(application_instance instance, DWORD reason, LPVOID reserved) +{ + SET_INSTANCE_HANDLE(instance); + // Remove this if you use lpReserved. + UNREFERENCED_PARAMETER(reserved); + + char *dll_name = DLL_NAME; + // mainly for debugging purposes. having the value for DLL_NAME actually + // stored should allow us to know which dll is being debugged. + + static CDynLinkLibrary *dll_link = NIL; + + static int dll_entry_count = 0; + + switch (reason) { + case DLL_PROCESS_ATTACH: { + char *message = DLL_NAME " Initializing!\n"; + TRACE_PRINTER(message); + + if (!check_DLL_versions()) return 0; + + // Extension DLL one-time initialization + if (!AfxInitExtensionModule(SomeDLL, instance)) return 0; + + // Insert this DLL into the resource chain. + dll_link = new CDynLinkLibrary(SomeDLL); + + // NOTE: If this Extension DLL is being implicitly linked to by an MFC + // Regular DLL (such as an ActiveX Control) instead of an MFC + // application, then you will want to remove this line from DllMain and + // put it in a separate function exported from this Extension DLL. The + // Regular DLL that uses this Extension DLL should then explicitly call + // that function to initialize this Extension DLL. Otherwise, the + // CDynLinkLibrary object will not be attached to the Regular DLL's + // resource chain, and serious problems will result. + ++dll_entry_count; + break; + } + case DLL_PROCESS_DETACH: { + --dll_entry_count; + char *message = DLL_NAME " Terminating!\n"; + TRACE_PRINTER(message); + // clean up our other stuff. + WHACK(dll_link); + // Terminate the library before destructors are called. + AfxTermExtensionModule(SomeDLL); + break; + } + case DLL_THREAD_ATTACH: + ++dll_entry_count; + break; + case DLL_THREAD_DETACH: + --dll_entry_count; + break; + default: +// do nothing. + break; + } + + return 1; +} + +#elif defined(__WIN32__) + +// regular dll base. + +#include // base windows stuff. + +bool check_DLL_versions(); + // supplied by the library's version of dllmain.cpp. + +BOOL APIENTRY DllMain(HANDLE module, DWORD ul_reason_for_call, LPVOID reserved) +{ + SET_INSTANCE_HANDLE((application_instance)module); + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + if (!check_DLL_versions()) return 0; + break; + + // these are currently not processed. + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return true; +} + +#endif + diff --git a/nucleus/library/application/hoople_main.h b/nucleus/library/application/hoople_main.h new file mode 100644 index 00000000..aac5a74e --- /dev/null +++ b/nucleus/library/application/hoople_main.h @@ -0,0 +1,122 @@ +#ifndef HOOPLE_MAIN_GROUP +#define HOOPLE_MAIN_GROUP + +/*****************************************************************************\ +* * +* Name : HOOPLE_MAIN group +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! @file "hoople_main.h" Provides macros that implement the 'main' program of an application. + +#include "application_shell.h" +#include "command_line.h" +#include "windoze_helper.h" + +#include +#include +#include +#include +#include + +namespace application { + +// The following versions of main programs are provided for different operating +// systems and compilation environments. These support the requirements of the +// HOOPLE startup code and program-wide features, assuming an object derived +// from base_application is available. +// The object derived from base_application must be provided in "obj_name". +// The "obj_args" are any arguments that need to be passed to the object's +// constructor; this list can be empty. +// Note that the default logger used for unix and console mode win32 programs +// is the console logger; that can be changed in the startup code for the +// "obj_name". + +#define HOOPLE_STARTUP_CODE \ + DEFINE_INSTANCE_HANDLE; + +#ifdef __WXWIDGETS__ + //! main program for applications using WxWidgets library. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + int main(int argc, char *argv[]) { \ + SET_ARGC_ARGV(argc, argv); \ + SETUP_COMBO_LOGGER; \ + obj_name to_run_obj obj_args; \ + return to_run_obj.execute_application(); \ + } + +////////////// + +#elif defined(__UNIX__) + //! options that should work for most unix and linux apps. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + int main(int argc, char *argv[]) { \ + SET_ARGC_ARGV(argc, argv); \ + SETUP_COMBO_LOGGER; \ + obj_name to_run_obj obj_args; \ + return to_run_obj.execute_application(); \ + } + +////////////// + +#elif defined(__WIN32__) + // for win32 we need to support four different environments--console mode, + // borland compilation, MFC programs and regular windows programs. + #ifdef _CONSOLE + //! console mode programs can easily write to a command shell. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + int main(int argc, char *argv[]) { \ + SETUP_COMBO_LOGGER; \ + SET_ARGC_ARGV(argc, argv); \ + obj_name to_run_obj obj_args; \ + return to_run_obj.execute_application(); \ + } + #elif defined(_AFXDLL) + //! MFC applications generally use a tiny shell which hooks up logging. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + SET_ARGC_ARGV(__argc, __argv); \ + tiny_application theApp; + #elif defined(__WIN32__) + //! standard win32 applications have no console, so we just log to a file. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + int WINAPI WinMain(application_instance instance, \ + application_instance prev_instance, LPSTR lpCmdLine, \ + int nCmdShow) { \ + SET_ARGC_ARGV(__argc, __argv); \ + SET_INSTANCE_HANDLE(instance); \ + SETUP_FILE_LOGGER; \ + obj_name to_run_obj obj_args; \ + return to_run_obj.execute_application(); \ + } + #endif + +////////////// + +#else // not __UNIX__ or __WIN32__ + //! just guessing this might work; otherwise we have no idea. + #define HOOPLE_MAIN(obj_name, obj_args) \ + HOOPLE_STARTUP_CODE; \ + int main(int argc, char *argv[]) { \ + SETUP_CONSOLE_LOGGER; \ + obj_name to_run_obj obj_args; \ + return to_run_obj.execute_application(); \ + } +#endif + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/hoople_service.cpp b/nucleus/library/application/hoople_service.cpp new file mode 100644 index 00000000..969a2c27 --- /dev/null +++ b/nucleus/library/application/hoople_service.cpp @@ -0,0 +1,224 @@ +////////////// +// Name : hoople_service +// Author : Chris Koeritz +////////////// +// Copyright (c) 2000-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "hoople_service.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; +using namespace timely; + +//#define DEBUG_HOOPLE_SERVICE + // uncomment for noisy version. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +namespace application { + +////////////// + +SAFE_STATIC(astring, hoople_service::_app_name, ) + +bool &hoople_service::_defunct() { static bool _defu = false; return _defu; } + +bool &hoople_service::_saw_interrupt() +{ static bool _saw = false; return _saw; } + +int &hoople_service::_timer_period() { static int _tim = 0; return _tim; } + +////////////// + +static hoople_service *_global_hoople_service = NIL; + // this static object is set by the setup() method. it should only come + // into existence once during a program's lifetime. + +hoople_service::hoople_service() +{ +} + +hoople_service::~hoople_service() +{ + make_defunct(); + if (_global_hoople_service) { + program_wide_timer().zap_timer(_global_hoople_service); + } +} + +void hoople_service::handle_startup() { /* nothing for base class. */ } + +void hoople_service::handle_shutdown() { /* nothing for base class. */ } + +void hoople_service::handle_timer() { /* nothing for base class. */ } + +void hoople_service::handle_timer_callback() +{ + // don't invoke the user's timer unless the object is still alive. + if (!is_defunct()) handle_timer(); +} + +void hoople_service::close_this_program() +{ + make_defunct(); +} + +bool hoople_service::close_application(const astring &app_name) +{ + FUNCDEF("close_application"); + process_entry_array procs; + process_control querier; + + // lookup process ids of apps. + bool got_list = querier.query_processes(procs); + if (!got_list) { + LOG(astring("couldn't get process list.")); + return false; + } + int_set pids; + got_list = querier.find_process_in_list(procs, app_name, pids); + if (!got_list) { + LOG(astring("couldn't find process in the list of active ones.")); + return true; + } + + // zap all of them using our signal. + for (int i = 0; i < pids.length(); i++) { +//would linux be better served with sigterm also? +#ifdef __UNIX__ + kill(pids[i], SIGHUP); +#endif +#ifdef __WIN32__ +//lame--goes to whole program. + raise(SIGTERM); +#endif +//hmmm: check results... + } + + return true; +} + +void hoople_service::handle_OS_signal(int formal(sig_id)) +{ + _saw_interrupt() = true; // save the status. + if (_global_hoople_service) { + _global_hoople_service->close_this_program(); + } +} + +void hoople_service::make_defunct() +{ + _defunct() = true; +} + +bool hoople_service::setup(const astring &app_name, int timer_period) +{ +//hmmm: make sure not already initted. + + // simple initializations first... + _timer_period() = timer_period; + _app_name() = app_name; + + _global_hoople_service = this; + + // setup signal handler for HUP signal. this is the one used to tell us + // to leave. +#ifdef __UNIX__ + signal(SIGHUP, handle_OS_signal); +#endif + + // setup a handler for interrupt (e.g. ctrl-C) also. + signal(SIGINT, handle_OS_signal); +#ifdef __WIN32__ + signal(SIGBREAK, handle_OS_signal); +#endif + + return true; +} + +bool hoople_service::launch_console(hoople_service &alert, + const astring &app_name, int timer_period) +{ +#ifdef DEBUG_HOOPLE_SERVICE + FUNCDEF("launch_console"); +#endif + if (!alert.setup(app_name, timer_period)) return false; + + alert.handle_startup(); // tell the program it has started up. + + // start a timer if they requested one. + if (_timer_period()) { + program_wide_timer().set_timer(_timer_period(), &alert); + } + +#ifdef DEBUG_HOOPLE_SERVICE + time_stamp next_report(10 * SECOND_ms); +#endif + + while (!alert.is_defunct()) { +#ifdef DEBUG_HOOPLE_SERVICE + if (time_stamp() >= next_report) { + printf("%s: shout out from my main thread yo.\n", _global_argv[0]); + next_report.reset(10 * SECOND_ms); + } +#endif + time_control::sleep_ms(42); + } + alert.handle_shutdown(); + return true; +} + +/* +#ifdef __WIN32__ +bool hoople_service::launch_event_loop(hoople_service &alert, + const astring &app_name, int timer_period) +{ + if (!alert.setup(app_name, timer_period)) return false; + alert.handle_startup(); + + if (timer_period) + program_wide_timer().set_timer(timer_period, this); + + MSG msg; + msg.hwnd = 0; msg.message = 0; msg.wParam = 0; msg.lParam = 0; + while (!alert.is_defunct() && (GetMessage(&msg, NIL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + alert.handle_shutdown(); + + return true; +} +#endif +*/ + +} //namespace. + diff --git a/nucleus/library/application/hoople_service.h b/nucleus/library/application/hoople_service.h new file mode 100644 index 00000000..8f02ff19 --- /dev/null +++ b/nucleus/library/application/hoople_service.h @@ -0,0 +1,137 @@ +#ifndef HOOPLE_SERVICE_CLASS +#define HOOPLE_SERVICE_CLASS + +////////////// +// Name : hoople_service +// Author : Chris Koeritz +////////////// +// Copyright (c) 2000-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include +#include +#include + +namespace application { + +//! A platform-independent way to alert a program that it should shut down immediately. +/*! + This can provide a service management feature for graceful shutdown of an + application, allowing it a chance to close its objects cleanly, rather than + just being whacked in the middle of whatever it's doing. + Only one of these objects should be instantiated per program, but the + static methods can be used from anywhere in the program. +*/ + +class hoople_service +: public virtual basis::root_object, public timely::timeable +{ +public: + hoople_service(); + //!< constructor does very little; setup() is what begins operation. + + virtual ~hoople_service(); + + DEFINE_CLASS_NAME("hoople_service"); + + bool setup(const basis::astring &app_name, int timer_period = 0); + //!< constructs a hoople_service for the "app_name" specified. + /*!< this can be any string, although it might be processed for certain + operating systems. also, for close_this_program() to work properly, it + must be the application's basename. the "timer_period" specifies how + frequently to invoke the handle_timer() method during runtime. if it's + zero, then no timer will be used. */ + + static bool is_defunct() { return _defunct(); } + //!< returns true if the object has been marked as defunct. + /*!< this means that it is either shutting down or soon will be. */ + + static void make_defunct(); + //!< used by the derived class to mark that this object is about to exit. + /*!< note that this can be used anywhere in the program to initiate an + exit of the program. */ + + bool saw_interrupt() { return _saw_interrupt(); } + //!< reports whether the process saw an interrupt from the user. + + // these virtual methods can be overridden by applications derived from the + // hoople_service. they support a graceful shutdown process by which + // applications can be alerted that they must shutdown, allowing them to take + // care of releasing resources beforehand. + + virtual void handle_startup(); + //!< this function is called once the program has begun operation. + + virtual void handle_shutdown(); + //!< called during the program's shutdown process. + /*!< this is invoked just prior to the destruction of this class which is + also just before the shutdown of the program overall. in this method, + the derived object must ensure that any threads the program started get + stopped, that any opened files get closed, and that any other resources + are released. this is the application's last chance to clean up. */ + + virtual void handle_timer(); + //!< called periodically if a timer period was specified. + + // static methods that can be used by the program for starting up or for + // graceful shutdown. + +//why? + static bool launch_console(hoople_service &alert, const basis::astring &app_name, + int timer_period = 0); + //!< this is used to begin execution of a console mode application. + /*!< this method does not do anything except sit while the extant threads + are in play. it will not return until the program must exit, as caused + by close_this_program() or close_application(). */ + +#if 0 //not implemented. +#ifdef __WIN32__ + static bool launch_event_loop(hoople_service &alert, + const basis::astring &app_name, int timer_period = 0); + //!< launches by starting up a windowing event loop. + /*!< this is appropriate for programs that are windowed and must + continually process window events. */ +#endif +#endif + + static void close_this_program(); + //!< causes this particular application to begin shutting down. + /*!< this is a static method available for programs that support the + hoople_service's graceful shutdown process. it causes the application + to begin the shutdown. */ + + static bool close_application(const basis::astring &app_name); + //!< attempts to close the application named "app_name". + /*!< this can only be done if this program possesses sufficient rights to + zap that program. */ + + // internal methods not to be used by outside objects. + + static void handle_OS_signal(int sig_id); + //!< processes the signal from the OS when its time to shut down. + +private: + static bool &_saw_interrupt(); //!< did we see a break from the user? + static basis::astring &_app_name(); //!< the of this application. + static bool &_defunct(); //!< is the program shutting down? + static int &_timer_period(); //!< rate at which timer goes off. + + virtual void handle_timer_callback(); //!< invoked by the timer driver. + + // not appropriate. + hoople_service(const hoople_service &); + hoople_service &operator =(const hoople_service &); +}; + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/application/launch_manager.cpp b/nucleus/library/application/launch_manager.cpp new file mode 100644 index 00000000..7e07c1e6 --- /dev/null +++ b/nucleus/library/application/launch_manager.cpp @@ -0,0 +1,804 @@ +/*****************************************************************************\ +* * +* Name : launch_manager +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "hoople_service.h" +#include "launch_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; + +namespace application { + +#define DEBUG_PROCESS_MANAGER + // uncomment for verbose diagnostics. + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +const int CHECK_INTERVAL = 4 * SECOND_ms; + // this is how frequently the checking thread executes to ensure that + // processes are gone when they should be. + +const int GRACEFUL_SLACK = 90 * SECOND_ms; + // the length of time before a graceful shutdown devolves into a forced + // shutdown. + +const int MAXIMUM_INITIAL_APP_WAIT = 4 * SECOND_ms; + // this is the longest we bother to wait for a process we just started. + // if it hasn't begun by then, we decide it will never do so. + +const int STARTUP_APPS_DELAY_PERIOD = 2 * SECOND_ms; + // we delay for this long before the initial apps are started. + +const int MAXIMUM_REQUEST_PAUSE = 42 * SECOND_ms; + // the longest we will ever wait for a response to be generated based on + // our last request. + +// these are concurrency control macros for the lists managed here. +#define LOCK_CONFIG auto_synchronizer l(*_config_lock) +#define LOCK_ZOMBIES auto_synchronizer l(*_zombie_lock) +#define LOCK_KIDS auto_synchronizer l(*_scamp_lock) + +// error messages. +#ifdef DEBUG_PROCESS_MANAGER + #define COMPLAIN_APPLICATION \ + LOG(astring("the application called ") + app_name + " could not be found.") + #define COMPLAIN_PRODUCT \ + LOG(astring("the section for ") + product + " could not be found.") +#else + #define COMPLAIN_APPLICATION {} + #define COMPLAIN_PRODUCT {} +#endif + +////////////// + +class launch_manager_thread : public ethread +{ +public: + launch_manager_thread(launch_manager &parent) + : ethread(CHECK_INTERVAL, ethread::SLACK_INTERVAL), + _parent(parent) {} + virtual ~launch_manager_thread() {} + + virtual void perform_activity(void *) + { _parent.push_timed_activities(_processes); } + +private: + launch_manager &_parent; // the owner of the object. + process_entry_array _processes; // will be filled as needed. +}; + +////////////// + +class graceful_record +{ +public: + astring _product; // the product name the app is listed under. + astring _app_name; // the application's name. + time_stamp _started; // when the graceful shutdown started. + int _pid; // the particular process id for this app. + int _level; // the shutdown ordering specifier. + + graceful_record(int pid = 0, const astring &product = "", + const astring &app_name = "", int level = 0) + : _product(product), _app_name(app_name), _pid(pid), _level(level) {} +}; + +class graceful_array : public array {}; + +////////////// + +launch_manager::launch_manager(configured_applications &config) +: _configs(config), + _started_initial_apps(false), + _checker(new launch_manager_thread(*this)), + _config_lock(new mutex), + _going_down(new graceful_array), + _zombie_lock(new mutex), + _our_kids(new graceful_array), + _scamp_lock(new mutex), + _stop_launching(false), + _startup_time(new time_stamp(STARTUP_APPS_DELAY_PERIOD)), + _procs(new process_control), + _gag_exclusions(new string_set), + _tracking_exclusions(new string_set) +{ +// FUNCDEF("constructor"); + + // start the application checking thread. + _checker->start(NIL); + + _checker->reschedule(200); // make it start pretty quickly. +} + +launch_manager::~launch_manager() +{ + FUNCDEF("destructor"); + stop_everything(); + + WHACK(_checker); + WHACK(_going_down); + WHACK(_our_kids); + WHACK(_scamp_lock); + WHACK(_zombie_lock); + WHACK(_config_lock); + WHACK(_startup_time); + WHACK(_procs); + WHACK(_gag_exclusions); + WHACK(_tracking_exclusions); + LOG("launch_manager is now stopped."); +} + +void launch_manager::add_gag_exclusion(const astring &exclusion) +{ *_gag_exclusions += exclusion; } + +void launch_manager::add_tracking_exclusion(const astring &exclusion) +{ *_tracking_exclusions += exclusion; } + +const char *launch_manager::outcome_name(const outcome &to_name) +{ + switch (to_name.value()) { + case FILE_NOT_FOUND: return "FILE_NOT_FOUND"; + case NO_ANCHOR: return "NO_ANCHOR"; + case NO_PRODUCT: return "NO_PRODUCT"; + case NO_APPLICATION: return "NO_APPLICATION"; + case BAD_PROGRAM: return "BAD_PROGRAM"; + case LAUNCH_FAILED: return "LAUNCH_FAILED"; + case NOT_RUNNING: return "NOT_RUNNING"; + case FROZEN: return "FROZEN"; + default: return common::outcome_name(to_name); + } +} + +void launch_manager::stop_everything() +{ + _stop_launching = true; // at least deny any connected clients. + stop_all_kids(); // shut down all programs that we started. + _checker->stop(); // stop our thread. +} + +void launch_manager::stop_all_kids() +{ + FUNCDEF("stop_all_kids"); + _stop_launching = true; // set this for good measure to keep clients out. + LOG("zapping any active sub-processes prior to exit."); + + // now we wait for the process closures to take effect. we are relying on + // the graceful shutdown devolving to a process zap and the timing is + // rooted around that assumption. + + for (int lev = 100; lev >= 0; lev--) { + // loop from our highest level to our lowest for the shutdown. +#ifdef DEBUG_PROCESS_MANAGER + LOG(a_sprintf("level %d", lev)); +#endif + bool zapped_any = false; + { + // this shuts down all the child processes we've started at this level. + LOCK_KIDS; // lock within this scope. + for (int i = _our_kids->length() - 1; i >= 0; i--) { + // now check each record and see if it's at the appropriate level. + graceful_record &grace = (*_our_kids)[i]; + if (lev == grace._level) { + // start a graceful shutdown. + zap_process(grace._product, grace._app_name, true); + // remove it from our list. + _our_kids->zap(i, i); + zapped_any = true; // set our flag. + } + } + } + int num_dying = 1; // go into the loop once at least. + +#ifdef DEBUG_PROCESS_MANAGER + time_stamp next_print(4 * SECOND_ms); +#endif + + while (num_dying) { +#ifdef DEBUG_PROCESS_MANAGER + if (time_stamp() >= next_print) { + LOG("waiting..."); + next_print.reset(4 * SECOND_ms); + } +#endif + + // while there are any pending process zaps, we will wait here. this + // will hose us but good if the processes aren't eventually cleared up, + // but that shouldn't happen. + + { + LOCK_ZOMBIES; + num_dying = _going_down->length(); + } + + if (!num_dying) break; // jump out of loop. + + _checker->reschedule(0); // make thread check as soon as possible. + + time_control::sleep_ms(40); + } +#ifdef DEBUG_PROCESS_MANAGER + LOG("done waiting..."); +#endif + } +} + +void launch_manager::launch_startup_apps() +{ + FUNCDEF("launch_startup_apps"); + + // read the startup section. + string_table startup_info; + { + LOCK_CONFIG; + if (!_configs.find_section(_configs.STARTUP_SECTION(), startup_info)) { + // if there's no startup section, we do nothing right now. + LOG("the startup section was not found!"); + return; + } + } +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("table has: ") + startup_info.text_form()); +#endif + + for (int i = 0; i < startup_info.symbols(); i++) { + astring app = startup_info.name(i); + if (app.equal_to(configured_applications::STARTUP_APP_NAME())) continue; + // skip bogus name that keeps the section present. + astring info = startup_info[i]; + LOG(astring("launching application ") + app + "..."); + // parse the items that are in the entry for this program. + astring product, parms; + bool one_shot; + if (!configured_applications::parse_startup_entry(info, product, parms, + one_shot)) { + LOG("the startup entry was not malformed; we could not parse it!"); + continue; + } + + LOG(app + a_sprintf(" is %ssingle shot.", one_shot? "" : "not ")); + + // now try to send the program off on its way. + launch_now(product, app, parms); + + if (one_shot) { + // it's only supposed to be started once, so now toss out the entry. + remove_from_startup(product, app); + } + } +} + +outcome launch_manager::launch_now(const astring &product, + const astring &app_name, const astring ¶meters) +{ + FUNCDEF("launch_now"); + LOG(astring("product \"") + product + "\", application \"" + app_name + + (parameters.length() ? astring("\"") + : astring("\", parms=") + parameters)); + + if (_stop_launching) { + // if the application is one of the exceptions to the gag rule, then + // we will still launch it. otherwise, we'll ignore this request. + if (_gag_exclusions->member(app_name)) { + // this is one of the apps that can still be launched when gagged. + } else { + LOG(astring("application \"") + app_name + "\" cannot be launched;" + + parser_bits::platform_eol_to_chars() + "launching services have been " + "shut down."); + return LAUNCH_FAILED; + } + } + + astring entry_found; + int level; + { + LOCK_CONFIG; + + // get the specific entry for the program they want. + entry_found = _configs.find_program(product, app_name, level); + if (!entry_found) { + if (!_configs.product_exists(product)) { + // return more specific error for missing product. + COMPLAIN_PRODUCT; + return NO_PRODUCT; + } + COMPLAIN_APPLICATION; + return NO_APPLICATION; + } + } + + filename existence_check(entry_found); + if (!existence_check.exists()) { + LOG(astring("file or path wasn't found for ") + entry_found + "."); + return FILE_NOT_FOUND; + } + + basis::un_int kid = 0; + int ret = launch_process::run(entry_found, parameters, + launch_process::RETURN_IMMEDIATELY | launch_process::HIDE_APP_WINDOW, kid); + if (!ret) { + // hey, it worked! so now make sure we track its lifetime... + + if (_tracking_exclusions->member(app_name)) { + // this is one of the apps that we don't track. if it's still + // running when we're shutting down, it's not our problem. + return OKAY; + } + + if (kid) { + // we were told the specific id for the process we started. + LOCK_KIDS; + LOG(a_sprintf("adding given process id %d for app %s at level %d.", + kid, app_name.s(), level)); + graceful_record to_add(kid, product, app_name, level); + *_our_kids += to_add; + return OKAY; + } +#ifdef DEBUG_PROCESS_MANAGER + LOG("was not told child process id!!!"); +#endif + // we weren't told the process id; let's see if we can search for it. + int_set pids; + time_stamp give_it_up(MAXIMUM_INITIAL_APP_WAIT); + while (give_it_up > time_stamp()) { + // find the process id for the program we just started. + if (find_process(app_name, pids)) break; + time_control::sleep_ms(10); // pause to see if we can find it yet. + } + + if (time_stamp() >= give_it_up) { + // we could not launch it for some reason, or our querier has failed. + // however, as far as we know, it launched successfully. if the id is + // missing, then that's not really our fault. we will just not be able + // to track the program, possibly because it's already gone. + LOG(astring("no process found for product \"") + product + + "\", application \"" + app_name + "\"; not adding " + "tracking record."); + return OKAY; + } + + LOCK_KIDS; + + // add all the processes we found under that name. + for (int i = 0; i < pids.elements(); i++) { + LOG(a_sprintf("adding process id %d for app %s at level %d.", + pids[i], app_name.s(), level)); + graceful_record to_add(pids[i], product, app_name, level); + *_our_kids += to_add; + } + return OKAY; + } + + // if we reached here, things are not good. we must not have been able to + // start the process. + +#ifdef __WIN32__ + if (ret == NO_ERROR) return OKAY; // how would that happen? + else if ( (ret == ERROR_FILE_NOT_FOUND) || (ret == ERROR_PATH_NOT_FOUND) ) { + LOG(astring("file or path wasn't found for ") + app_name + "."); + return FILE_NOT_FOUND; + } else if (ret == ERROR_BAD_FORMAT) { + LOG(astring(app_name) + " was not in EXE format."); + return BAD_PROGRAM; + } else { + LOG(astring("there was an unknown error while trying to run ") + + app_name + "; the error is:" + parser_bits::platform_eol_to_chars() + + critical_events::system_error_text(ret)); + return LAUNCH_FAILED; + } +#else + LOG(astring("error ") + critical_events::system_error_text(ret) + + " occurred attempting to run: " + app_name + " " + parameters); + return FILE_NOT_FOUND; +#endif +} + +outcome launch_manager::launch_at_startup(const astring &product, + const astring &app_name, const astring ¶meters, int one_shot) +{ + FUNCDEF("launch_at_startup"); + LOCK_CONFIG; // this whole function modifies the config file. + +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("product \"") + product + "\", application \"" + app_name + + (one_shot? astring("\", OneShot") : astring("\", MultiUse"))); +#endif + + // get the specific entry for the program they want. + int level; + astring entry_found = _configs.find_program(product, app_name, level); + if (!entry_found) { + if (!_configs.product_exists(product)) { + // return more specific error for missing product. + COMPLAIN_PRODUCT; + return NO_PRODUCT; + } + COMPLAIN_APPLICATION; + return NO_APPLICATION; + } + + if (!_configs.add_startup_entry(product, app_name, parameters, one_shot)) { + // most likely problem is that it was already there. + return EXISTING; + } + + return OKAY; +} + +outcome launch_manager::remove_from_startup(const astring &product, + const astring &app_name) +{ + FUNCDEF("remove_from_startup"); + LOCK_CONFIG; // this whole function modifies the config file. + +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); +#endif + + // get the specific entry for the program they want. + int level; + astring entry_found = _configs.find_program(product, app_name, level); + if (!entry_found) { + if (!_configs.product_exists(product)) { + // return more specific error for missing product. + COMPLAIN_PRODUCT; + return NO_PRODUCT; + } + COMPLAIN_APPLICATION; + return NO_APPLICATION; + } +//hmmm: is product required for this for real? + + if (!_configs.remove_startup_entry(product, app_name)) { + // the entry couldn't be removed, probably doesn't exist. + return NO_APPLICATION; + } + + return OKAY; +} + +outcome launch_manager::query_application(const astring &product, + const astring &app_name) +{ + FUNCDEF("query_application"); +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); +#endif + + { + LOCK_CONFIG; + // get the specific entry for the program they want. + int level; + astring entry_found = _configs.find_program(product, app_name, level); + if (!entry_found) { + if (!_configs.product_exists(product)) { + // return more specific error for missing product. + COMPLAIN_PRODUCT; + return NO_PRODUCT; + } + COMPLAIN_APPLICATION; + return NO_APPLICATION; + } + } + + // now seek that program name in the current list of processes. + astring program_name(app_name); + program_name.to_lower(); + + int_set pids; + if (!find_process(app_name, pids)) + return NOT_RUNNING; + return OKAY; +} + +outcome launch_manager::start_graceful_close(const astring &product, + const astring &app_name) +{ + FUNCDEF("start_graceful_close"); +//hmmm: record this app as one we need to watch. + + { + // find that program name to make sure it's not already shutting down. + LOCK_ZOMBIES; + + for (int i = _going_down->length() - 1; i >= 0; i--) { + graceful_record &grace = (*_going_down)[i]; + if (grace._app_name.iequals(app_name)) { + return OKAY; + } + } + } + + if (!hoople_service::close_application(app_name)) + return NO_ANCHOR; + + int_set pids; + if (!find_process(app_name, pids)) { + LOG(astring("Failed to find process id for [") + app_name + + astring("], assuming it has already exited.")); + return OKAY; + } + + { + // add all the process ids, just in case there were multiple instances + // of the application somehow. + LOCK_ZOMBIES; + for (int i = 0; i < pids.elements(); i++) { + graceful_record to_add(pids[i], product, app_name); + *_going_down += to_add; + } + } + + return OKAY; +} + +bool launch_manager::get_processes(process_entry_array &processes) +{ + FUNCDEF("get_processes"); + if (!_procs->query_processes(processes)) { + LOG("failed to query processes!"); + return false; + } + return true; +} + +bool launch_manager::find_process(const astring &app_name_in, int_set &pids) +{ +// FUNCDEF("find_process"); + pids.clear(); + process_entry_array processes; + if (!get_processes(processes)) return false; + return process_control::find_process_in_list(processes, app_name_in, pids); +} + +outcome launch_manager::zap_process(const astring &product, + const astring &app_name_key, bool graceful) +{ + FUNCDEF("zap_process"); + +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("product \"") + product + "\", application \"" + app_name_key + + (graceful? "\", Graceful Close" : "\", Brutal Close")); +#endif + + if (_tracking_exclusions->member(app_name_key)) { + // the non-tracked applications are never reported as running since they're + // not allowed to be zapped anyhow. + return NOT_RUNNING; + } + + // use the real program name from here onward. + astring app_name; + { + LOCK_CONFIG; + + // get the specific entry for the program they want. + int level; + app_name = _configs.find_program(product, app_name_key, level); + if (!app_name) { + if (!_configs.product_exists(product)) { + // return more specific error for missing product. + COMPLAIN_PRODUCT; + return NO_PRODUCT; + } + COMPLAIN_APPLICATION; + return NO_APPLICATION; + } + // truncate the directory and just use the base. + app_name = filename(app_name).basename(); + } + + // if they want a graceful close, start by trying that. if it appears to + // have succeeded, then we exit and let the thread take care of ensuring + // the app goes down. + outcome to_return = NOT_RUNNING; + if (graceful) + to_return = start_graceful_close(product, app_name); + if (to_return == OKAY) + return OKAY; // maybe finished the close. + + // they want a harsh close of this application or the graceful close failed. + astring program_name(app_name); + int_set pids; + if (!find_process(program_name, pids)) { +#ifdef DEBUG_PROCESS_MANAGER + LOG(program_name + " process was not running.") +#endif + return NOT_RUNNING; + } + + // search for the application in the process list. + bool failed = false; + for (int i = 0; i < pids.elements(); i++) { + bool ret = _procs->zap_process(pids[i]); + if (ret) { + LOG(astring(astring::SPRINTF, "Killed process %d [", + pids[i]) + program_name + astring("]")); + } else { + LOG(astring(astring::SPRINTF, "Failed to zap process %d [", + pids[i]) + program_name + astring("]")); + } + if (!ret) failed = true; + } +// kind of a bizarre return, but whatever. it really should be some +// failure result since the zap failed. + return failed? ACCESS_DENIED : OKAY; +} + +outcome launch_manager::shut_down_launching_services(const astring &secret_word) +{ + FUNCDEF("shut_down_launching_services"); + LOG("checking secret word..."); +//hmmm: changing the secret word here. + if (secret_word.equal_to("MarblesAreRoundishYo")) { + LOG("it's correct; ending launch capabilities."); + } else { + LOG("the secret word is wrong. continuing normal operation."); + return BAD_PROGRAM; + } + _stop_launching = true; + return OKAY; +} + +outcome launch_manager::reenable_launching_services(const astring &secret_word) +{ + FUNCDEF("reenable_launching_services"); + LOG("checking secret word..."); + if (secret_word.equal_to("MarblesAreRoundishYo")) { + LOG("it's correct; resuming launch capabilities."); + } else { + LOG("the secret word is wrong. continuing with prior mode."); + return BAD_PROGRAM; + } + _stop_launching = false; + return OKAY; +} + +#define GET_PROCESSES \ + if (!retrieved_processes) { \ + retrieved_processes = true; \ + if (!get_processes(processes)) { \ + LOG("failed to retrieve process list from OS!"); \ + return; /* badness. */ \ + } \ + } + +void launch_manager::push_timed_activities(process_entry_array &processes) +{ + FUNCDEF("push_timed_activities"); + + // make sure we started the applications that were slated for execution at + // system startup time. we wait on this until the first thread activation + // to give other processes some breathing room right at startup time. + // also, it then doesn't block the main service thread. + if (!_started_initial_apps && (*_startup_time <= time_stamp()) ) { + // launch all the apps that are listed for system startup. + LOG("starting up the tasks registered for system initiation."); + launch_startup_apps(); + _started_initial_apps = true; + } + + if (!_started_initial_apps) { + _checker->reschedule(200); + // keep hitting this function until we know we can relax since the + // startup apps have been sent off. + } + + bool retrieved_processes = false; + + { + // loop over the death records we've got and check on the soon to be gone. + LOCK_ZOMBIES; + + for (int i = _going_down->length() - 1; i >= 0; i--) { + graceful_record &grace = (*_going_down)[i]; + + GET_PROCESSES; // load them if they hadn't been. + + int_set pids; + if (!process_control::find_process_in_list(processes, grace._app_name, + pids)) { + // the app can't be found as running, so whack the record for it. +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("cannot find app ") + grace._app_name + + " as running still; removing its dying record"); +#endif + _going_down->zap(i, i); + continue; + } + if (!pids.member(grace._pid)) { + // that particular instance exited on its own, so whack the record. +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("app ") + grace._app_name + + " exited on its own; removing its dying record"); +#endif + _going_down->zap(i, i); + continue; + } + + if (grace._started <= time_stamp(-GRACEFUL_SLACK)) { + // time to force it. + LOG(astring("Application ") + grace._app_name + " is unresponsive to " + "the graceful shutdown request. Now zapping it instead."); + if (!_procs->zap_process(grace._pid)) + LOG("Devolved graceful shutdown failed as zap_process also."); + _going_down->zap(i, i); + continue; + } + + // it's not time to dump this one yet, so keep looking at others. + } + } + + { + // now loop over the list of our active kids and make sure that they are + // all still running. if they aren't, then toss the record out. + LOCK_KIDS; + + for (int i = _our_kids->length() - 1; i >= 0; i--) { + graceful_record &grace = (*_our_kids)[i]; + + GET_PROCESSES; // load them if they hadn't been. + + int_set pids; + if (!process_control::find_process_in_list(processes, grace._app_name, + pids)) { + // the app can't be found as running, so whack the record for it. +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("cannot find kid ") + grace._app_name + + " as still running; removing its normal record"); +#endif + _our_kids->zap(i, i); + continue; + } + if (!pids.member(grace._pid)) { + // that particular instance exited on its own, so whack the record. +#ifdef DEBUG_PROCESS_MANAGER + LOG(astring("kid ") + grace._app_name + + " exited on its own; removing its normal record"); +#endif + _our_kids->zap(i, i); + continue; + } + + // this kid is still going, so keep its record. + } + } +} + +} //namespace. + diff --git a/nucleus/library/application/launch_manager.h b/nucleus/library/application/launch_manager.h new file mode 100644 index 00000000..33e4aef8 --- /dev/null +++ b/nucleus/library/application/launch_manager.h @@ -0,0 +1,186 @@ + +// current bad issues: +// +// this class does not really provide a notion of the process name AND process id as being +// a pair one can operate on. mostly it assumes a bunch of singletons or that it's okay +// to whack all of them. +// that could be fixed by making the procname+procid pair into a unit in all functions, +// and make kill_all be a specialization of that. + + +#ifndef LAUNCH_MANAGER_CLASS +#define LAUNCH_MANAGER_CLASS + +/*****************************************************************************\ +* * +* Name : launch_manager +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace application { + +class graceful_array; +class launch_manager_thread; + +//! Provides methods for starting, stopping and checking on processes. +/*! + This includes support for graceful shutdowns and background handling of + exiting processes. +*/ + +class launch_manager : public virtual basis::root_object +{ +public: + launch_manager(processes::configured_applications &config); + //!< the launch_manager needs a configuration set to work with. + + virtual ~launch_manager(); + + DEFINE_CLASS_NAME("launch_manager"); + + enum outcomes { + OKAY = basis::common::OKAY, + EXISTING = basis::common::EXISTING, + //!< the entry already exists and overwriting is disallowed. + ACCESS_DENIED = basis::common::ACCESS_DENIED, + //!< the requested operation was not permitted. + + DEFINE_API_OUTCOME(FILE_NOT_FOUND, -53, "The file specified for the " + "application doesn't exist, as far as we can tell"), + DEFINE_API_OUTCOME(NO_PRODUCT, -54, "The product specified does not exist"), + DEFINE_API_OUTCOME(NO_APPLICATION, -55, "The application is not listed for " + "the product"), + DEFINE_API_OUTCOME(NOT_RUNNING, -56, "The program is not currently active, " + "according to the OS process list"), + DEFINE_API_OUTCOME(BAD_PROGRAM, -57, "The file existed but doesn't appear " + "to be a valid program image"), + DEFINE_API_OUTCOME(NO_ANCHOR, -58, "This occurs when the graceful shutdown " + "process cannot find the special anchor window that implements the " + "client side of a graceful shutdown"), + DEFINE_API_OUTCOME(LAUNCH_FAILED, -59, "The program existed and seemed " + "valid but its launch failed for some reason"), + DEFINE_API_OUTCOME(FROZEN, -60, "The application is broken somehow; the " + "system reports it as non-responsive"), + // note that these values are reversed, since the ordering is negative. + FIRST_OUTCOME = FROZEN, // hold onto start of range. + LAST_OUTCOME = FILE_NOT_FOUND // hold onto end of range. + }; + + static const char *outcome_name(const basis::outcome &to_name); + //!< returns the text associated with "to_name". + + basis::outcome launch_now(const basis::astring &product, const basis::astring &app_name, + const basis::astring ¶meters); + //!< starts the application "app_name" now. + /*!< causes the program with "app_name" that's listed for the "product" to + be started with the "parameters". this can fail if the application + isn't listed or if the program can't be found or if the process can't + be created. */ + + basis::outcome launch_at_startup(const basis::astring &product, const basis::astring &app_name, + const basis::astring ¶meters, int one_shot); + //!< records an entry for the "app_name" to be launched at startup. + /*!< this does not launch the application now. if "one_shot" is true, the + application will only be started once and then removed from the list. */ + + basis::outcome remove_from_startup(const basis::astring &product, const basis::astring &app_name); + //!< takes the "app_name" out of the startup list. + + basis::outcome query_application(const basis::astring &product, const basis::astring &app_name); + //!< retrieves the current state of the program with "app_name". + + basis::outcome zap_process(const basis::astring &product, const basis::astring &app_name, + bool graceful); + //!< zaps the process named "app_name". + /*!< if "graceful" is true, then the clean shutdown process is attempted. + otherwise the application is harshly terminated. */ + + void add_gag_exclusion(const basis::astring &exclusion); + //!< add an application that isn't subject to gagging. + /*!< if the launch_manager is gagged, the excluded applications can + still be started. */ + void add_tracking_exclusion(const basis::astring &exclusion); + //!< apps that aren't tracked when running. + /*!< if a zap is attempted on one of these applications, then + the process is not shut down. */ + + basis::outcome shut_down_launching_services(const basis::astring &secret_word); + //!< closes down the ability of clients to launch applications. + /*!< the checking and shut down functions continue to operate. this is + intended for use prior to a restart of the application controller, in + order to ensure that no remote clients can start new servers on this + machine. */ + + basis::outcome reenable_launching_services(const basis::astring &secret_word); + //!< undoes the gagging that the above "shut_down" function does. + /*!< this allows the launch_manager to continue operating normally. */ + + bool services_disabled() const { return _stop_launching; } + //!< returns true if the capability to launch new processes is revoked. + + void push_timed_activities(processes::process_entry_array &processes); + //!< keeps any periodic activities going. + /*!< this includes such tasks as zapping processes that have gone beyond + their time limit for graceful shutdown. */ + + void stop_everything(); + //!< closes down the operation of this object. + +private: + processes::configured_applications &_configs; //!< manages the entries for companies. + bool _started_initial_apps; //!< true if we launched the boot apps. + launch_manager_thread *_checker; //!< keeps periodic activities going. + basis::mutex *_config_lock; //!< the synchronizer for our configuration entries. + graceful_array *_going_down; //!< record of graceful shutdowns in progress. + basis::mutex *_zombie_lock; //!< the synchronizer for the dying processes. + graceful_array *_our_kids; //!< the processes we've started. + basis::mutex *_scamp_lock; //!< the synchronizer for the list of children. + bool _stop_launching; //!< true if no launches should be allowed any more. + timely::time_stamp *_startup_time; + //!< the time we feel it's safe to launch the startup apps. + /*!< we delay this some so that the launch_manager doesn't immediately + soak up too much CPU. */ + processes::process_control *_procs; //!< gives us access to the process list. + structures::string_set *_gag_exclusions; //!< apps that aren't subject to gag law. + structures::string_set *_tracking_exclusions; //!< apps that aren't tracked when running. + + bool get_processes(processes::process_entry_array &processes); + //!< grabs the list of "processes" or returns false. + + bool find_process(const basis::astring &app_name, structures::int_set &pids); + //!< locates any instances of "app_name" and returns process ids in "pids". + + basis::outcome start_graceful_close(const basis::astring &product, const basis::astring &app_name); + //!< attempts to close the "app_name". + /*!< if the application doesn't shut down within the time limit, it is + eventually zapped harshly. */ + + void launch_startup_apps(); + //!< iterates over the list of startup apps and creates a process for each. + + void stop_all_kids(); + //!< closes all dependent processes when its time to stop the service. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/application/makefile b/nucleus/library/application/makefile new file mode 100644 index 00000000..758240c5 --- /dev/null +++ b/nucleus/library/application/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = application +TYPE = library +SOURCE = application_shell.cpp callstack_tracker.cpp command_line.cpp dll_root.cpp \ + hoople_service.cpp launch_manager.cpp memory_checker.cpp redirecter.cpp shared_memory.cpp \ + singleton_application.cpp windoze_helper.cpp +TARGETS = application.lib +LOCAL_LIBS_USED = basis + +include cpp/rules.def + diff --git a/nucleus/library/application/memory_checker.cpp b/nucleus/library/application/memory_checker.cpp new file mode 100644 index 00000000..2426c785 --- /dev/null +++ b/nucleus/library/application/memory_checker.cpp @@ -0,0 +1,414 @@ + + + +/*****************************************************************************\ +* * +* Name : memory_checker * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// note: parts of this have been around since at least 1998, but this code was +// newly revised for memory checking in february of 2007. --cak + +#ifdef ENABLE_MEMORY_HOOK + +#include "definitions.h" +#include "log_base.h" +#include "memory_checker.h" +#include "mutex.h" +#include "utility.h" + +#include +#include +#include + +const int MAXIMUM_HASH_SLOTS = 256 * KILOBYTE; + // that's a whole lot of slots. this number is basically multiplied by + // the sizeof(memory_bin) to get full memory footprint. + +const int SINGLE_LINE_SIZE_ESTIMATE = 200; + // we are guessing that the average line of memory printout will take + // this many characters. that includes the size, the pointer value, + // and the location and line number. + +const int RESERVED_AREA = 1000; + // we reserve this much space in the string generated for the memory bin + // dumps. it will be used for adding more information to the string. + +#define CLEAR_ALLOCATED_MEMORY + // uncomment this to ensure that new memory gets its contents set to zero. + // neither new nor malloc does this, but it can help finding bugs from + // people re-using deallocated memory. + +#define MEMORY_CHECKER_STATISTICS + // uncomment to enable code that analyzes how many allocations were new and + // so forth. this will make the object run a bit slower. + +//#define DEBUG_MEMORY_CHECKER + // uncomment for super noisy version. + +////////////// + +// define the replacement new and delete operators. + +#include +void *operator new(size_t size, char *file, int line) throw (std::bad_alloc) +{ return program_wide_memories().provide_memory(size, file, line); } +#include + +void operator delete(void *ptr) throw () +{ program_wide_memories().release_memory(ptr); } + +////////////// + +// memlink is one link in a chain of memories. it's singly linked, so our +// algorithms have to explicitly remember the parent. + +class memlink +{ +public: + void *_chunk; //!< the chunk of memory held (not real address). + /*!< NOTE: we store the chunk as it looks to the outside world, rather + than using its real address. this eliminates a bit of ambiguity in the + code. */ + memlink *_next; //!< the next memory wrapper in the list. + int _size; //!< the size of the chunk delivered. + char *_where; //!< the name of the place that created it. + int _line; //!< the line number where the item was allocated. +#ifdef ENABLE_CALLSTACK_TRACKING + char *_stack; //!< records the stack seen at time of allocation. +#endif + + void construct(void *ptr, int size, char *where, int line) { + _next = NIL; + _chunk = ptr; + _size = size; + _where = strdup(where); // uses malloc, not new, so we're safe. + if (strlen(_where) > SINGLE_LINE_SIZE_ESTIMATE - 40) { + // if we will not have room for the full line, we crop it. + _where[SINGLE_LINE_SIZE_ESTIMATE - 40] = '\0'; + } + _line = line; +#ifdef ENABLE_CALLSTACK_TRACKING + _stack = program_wide_stack_trace().full_trace(); +///printf("stack here:\n%s", _stack); +#endif + } + + void destruct() { + free(_chunk); _chunk = NIL; + free(_where); _where = NIL; + _next = NIL; + _size = 0; + _line = 0; +#ifdef ENABLE_CALLSTACK_TRACKING + free(_stack); _stack = NIL; +#endif + } +}; + +////////////// + +//pretty lame here so far. +#ifdef MEMORY_CHECKER_STATISTICS + // simple stats: the methods below will tweak these numbers if memory_checker + // statistics are enabled. ints won't do here, due to the number of + // operations in a long-running program easily overflowing that size. + + // this bank of statistics counts the number of times memory was treated + // a certain way. + double _stat_new_allocations = 0; // this many new allocations occurred. + double _stat_freed_allocations = 0; // number of freed blocks. + // next bank of stats are the sizes of the memory that were stowed, etc. + double _stat_new_allocations_size = 0; // this many bytes got allocated. + double _stat_freed_allocations_size = 0; // this many bytes were freed. +#endif + +////////////// + +//! the memory bin holds a list of chunks of memory in memlink objects. + +class memory_bin +{ +public: + void construct() { + _head = NIL; + _count = 0; + _lock = (mutex_base *)malloc(sizeof(mutex_base)); + _lock->construct(); + } + void destruct() { + _lock->destruct(); + free(_lock); + } + + int count() const { return _count; } + + int record_memory(void *ptr, int size, char *where, int line) { + memlink *new_guy = (memlink *)malloc(sizeof(memlink)); + new_guy->construct(ptr, size, where, line); + _lock->lock(); + // this code has the effect of putting more recent allocations first. + // if they happen to get cleaned up right away, that's nice and fast. + new_guy->_next = _head; + _head = new_guy; + _count++; + _lock->unlock(); + return common::OKAY; // seems to have worked fine. + } + + int release_memory(void *to_release) { + _lock->lock(); + // search the bin to locate the item specified. + memlink *current = _head; // current will scoot through the list. + memlink *previous = NIL; // previous remembers the parent node, if any. + while (current) { + if (current->_chunk == to_release) { +#ifdef MEMORY_CHECKER_STATISTICS + // record that it went away. + _stat_freed_allocations += 1.0; + _stat_freed_allocations_size += current->_size; +#endif +#ifdef DEBUG_MEMORY_CHECKER + printf("found %p listed, removing for %s[%d]\n", to_release, + current->_where, current->_line); +#endif + // unlink this one and clean up; they don't want it now. + if (!previous) { + // this is the head we're modifying. + _head = current->_next; + } else { + // not the head, so there was a valid previous element. + previous->_next = current->_next; + } + // now trash that goner's house. + current->destruct(); + free(current); + _count--; + _lock->unlock(); + return common::OKAY; + } + // the current node isn't it; jump to next node. + previous = current; + current = current->_next; + } +#ifdef DEBUG_MEMORY_CHECKER + printf("failed to find %p listed.\n", to_release); +#endif + _lock->unlock(); + return common::NOT_FOUND; + } + + void dump_list(char *add_to, int &curr_size, int max_size) { + int size_alloc = 2 * SINGLE_LINE_SIZE_ESTIMATE; // room for one line. + char *temp_str = (char *)malloc(size_alloc); + memlink *current = _head; // current will scoot through the list. + while (current) { + temp_str[0] = '\0'; + sprintf(temp_str, "\n\"%s[%d]\", \"size %d\", \"addr %p\"\n", + current->_where, current->_line, current->_size, current->_chunk); + int len_add = strlen(temp_str); + if (curr_size + len_add < max_size) { + strcat(add_to, temp_str); + curr_size += len_add; + } +#ifdef ENABLE_CALLSTACK_TRACKING + len_add = strlen(current->_stack); + if (curr_size + len_add < max_size) { + strcat(add_to, current->_stack); + curr_size += len_add; + } +#endif + current = current->_next; + } + free(temp_str); + } + +private: + memlink *_head; // our first, if any, item. + mutex_base *_lock; // protects our bin from concurrent access. + int _count; // current count of items held. +}; + +////////////// + +class allocation_memories +{ +public: + void construct(int num_slots) { + _num_slots = num_slots; + _bins = (memory_bin *)malloc(num_slots * sizeof(memory_bin)); + for (int i = 0; i < num_slots; i++) + _bins[i].construct(); + } + + void destruct() { + // destroy each bin in our list. + for (int i = 0; i < _num_slots; i++) { + _bins[i].destruct(); + } + free(_bins); + _bins = NIL; + } + + int compute_slot(void *ptr) { + return utility::hash_bytes(&ptr, sizeof(void *)) % _num_slots; + } + + void *provide_memory(int size_needed, char *file, int line) { + void *new_allocation = malloc(size_needed); + // slice and dice pointer to get appropriate hash bin. + int slot = compute_slot(new_allocation); +#ifdef DEBUG_MEMORY_CHECKER + printf("using slot %d for %p\n", slot, new_allocation); +#endif + _bins[slot].record_memory(new_allocation, size_needed, file, line); +#ifdef MEMORY_CHECKER_STATISTICS + _stat_new_allocations += 1.0; + _stat_new_allocations_size += size_needed; +#endif + return new_allocation; + } + + int release_memory(void *to_drop) { + int slot = compute_slot(to_drop); // slice and dice to get bin number. +#ifdef DEBUG_MEMORY_CHECKER + printf("removing mem %p from slot %d.\n", to_drop, slot); +#endif + return _bins[slot].release_memory(to_drop); + } + + //! this returns a newly created string with all current contents listed. + /*! this returns a simple char * pointer that was created with malloc. + the invoker *must* free the returned pointer. we use malloc/free here to + avoid creating a wacky report that contains a lot of new allocations for + the report itself. that seems like it could become a problem. */ + char *report_allocations() { + // count how many allocations we have overall. + int full_count = 0; + for (int i = 0; i < _num_slots; i++) { +///if (_bins[i].count()) printf("%d adding count %d\n", i, _bins[i].count()); + full_count += _bins[i].count(); + } +///printf("full count is %d\n", full_count); + // calculate a guess for how much space we need to show all of those. + int alloc_size = full_count * SINGLE_LINE_SIZE_ESTIMATE + RESERVED_AREA; + char *to_return = (char *)malloc(alloc_size); + to_return[0] = '\0'; + if (full_count) { + strcat(to_return, "===================\n"); + strcat(to_return, "Unfreed Allocations\n"); + strcat(to_return, "===================\n"); + } + int curr_size = strlen(to_return); // how much in use so far. + for (int i = 0; i < _num_slots; i++) { + _bins[i].dump_list(to_return, curr_size, alloc_size - RESERVED_AREA); + } + return to_return; + } + + // this is fairly resource intensive, so don't dump the state out that often. + char *text_form(bool show_outstanding) { + char *to_return = NIL; + if (show_outstanding) { + to_return = report_allocations(); + } else { + to_return = (char *)malloc(RESERVED_AREA); + to_return[0] = '\0'; + } +#ifdef MEMORY_CHECKER_STATISTICS + char *temp_str = (char *)malloc(4 * SINGLE_LINE_SIZE_ESTIMATE); + + sprintf(temp_str, "=================\n"); + strcat(to_return, temp_str); + sprintf(temp_str, "Memory Statistics\n"); + strcat(to_return, temp_str); + sprintf(temp_str, "=================\n"); + strcat(to_return, temp_str); + sprintf(temp_str, "Measurements taken across entire program runtime:\n"); + strcat(to_return, temp_str); + sprintf(temp_str, " %.0f new allocations.\n", _stat_new_allocations); + strcat(to_return, temp_str); + sprintf(temp_str, " %.4f new Mbytes.\n", + _stat_new_allocations_size / MEGABYTE); + strcat(to_return, temp_str); + sprintf(temp_str, " %.0f freed deallocations.\n", + _stat_freed_allocations); + strcat(to_return, temp_str); + sprintf(temp_str, " %.4f freed Mbytes.\n", + _stat_freed_allocations_size / MEGABYTE); + strcat(to_return, temp_str); + + free(temp_str); +#endif + return to_return; + } + +private: + memory_bin *_bins; //!< each bin manages a list of pointers, found by hash. + int _num_slots; //!< the number of hash slots we have. +}; + +////////////// + +void memory_checker::construct() +{ + _mems = (allocation_memories *)malloc(sizeof(allocation_memories)); + _mems->construct(MAXIMUM_HASH_SLOTS); + _unusable = false; + _enabled = true; +} + +void memory_checker::destruct() +{ + if (_unusable) return; // already gone. +if (!_mems) printf("memory_checker::destruct being invoked twice!\n"); + + // show some stats about memory allocation. + char *mem_state = text_form(true); + printf("%s", mem_state); +///uhhh free(mem_state); +//the above free seems to totally die if we allow it to happen. + + _unusable = true; + + _mems->destruct(); + free(_mems); + _mems = NIL; +} + +void *memory_checker::provide_memory(size_t size, char *file, int line) +{ + if (_unusable || !_enabled) return malloc(size); + return _mems->provide_memory(size, file, line); +} + +int memory_checker::release_memory(void *ptr) +{ + if (_unusable || !_enabled) { + free(ptr); + return common::OKAY; + } + return _mems->release_memory(ptr); +} + +char *memory_checker::text_form(bool show_outstanding) +{ + if (_unusable) return strdup("already destroyed memory_checker!\n"); + return _mems->text_form(show_outstanding); +} + +////////////// + +#endif // enable memory hook + + + diff --git a/nucleus/library/application/memory_checker.h b/nucleus/library/application/memory_checker.h new file mode 100644 index 00000000..d899abd4 --- /dev/null +++ b/nucleus/library/application/memory_checker.h @@ -0,0 +1,99 @@ +#ifndef MEMORY_CHECKER_CLASS +#define MEMORY_CHECKER_CLASS + +/*****************************************************************************\ +* * +* Name : memory_checker * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "definitions.h" + +#ifdef ENABLE_MEMORY_HOOK + +#include "build_configuration.h" +uhh + +// forward. +class allocation_memories; +class memory_checker; + +////////////// + +memory_checker BASIS_EXTERN &program_wide_memories(); + //!< a global version of the memory checker to access memory tracking. + /*!< this accesses the singleton object that tracks all memory allocations + occuring in the program via calls to new and delete. it should be used + rather than creating a memory_checker anywhere else. */ + +////////////// + +//! Debugging assistance tool that checks for memory leaks. +/*! + Provides allocation checking for heap memory for C++. This is used as a + replacement for the standard new and delete operations. No object should + really need to deal with this class directly; it is hooked in automatically + unless the ENABLE_MEMORY_HOOK macro is defined. Generally, release builds + should not have this enabled, since it will slow things down tremendously. + NOTE: this object can absolutely not be hooked into callstack tracker, since + this object implements all c++ memory, including the tracker's. This object + will use the backtrace information if it's available. +*/ + +class memory_checker +{ +public: + void construct(); //!< faux constructor for mallocing. + + void destruct(); //!< faux destructor shuts down object. + + //! turn off memory checking. + void disable() { _enabled = false; } + //! turn memory checking back on. + void enable() { _enabled = true; } + //! reports on whether the memory checker is currently in service or not. + bool enabled() const { return _enabled; } + + void *provide_memory(size_t size, char *file, int line); + //!< returns a chunk of memory with the "size" specified. + /*!< this is the replacement method for the new operator. we will be + calling this instead of the compiler provided new. the "file" string + should be a record of the location where this is invoked, such as is + provided by the __FILE__ macro. the "line" should be set to the line + number within the "file", if applicable (use __LINE__). */ + + int release_memory(void *ptr); + //!< drops our record for the memory at "ptr". + /*!< this is the only way to remove an entry from our listings so that + it will not be reported as a leak. we do not currently gather any info + about where the release is invoked. if there was a problem, the returned + outcome will not be OKAY. */ + + char *text_form(bool show_outstanding); + //!< returns a newly allocated string with the stats for this object. + /*!< if "show_outstanding" is true, then all outstanding allocations are + displayed in the string also. the invoker *must* free the returned + pointer. */ + +private: + allocation_memories *_mems; //!< internal object tracks all allocations. + bool _unusable; //!< true after destruct is called. + bool _enabled; //!< true if the object is okay to use. +}; + +#else // enable memory hook. + // this section disables the memory checker entirely. + #define program_wide_memories() + // define a do nothing macro for the global memory tracker. +#endif // enable memory hook. + +#endif // outer guard. + diff --git a/nucleus/library/application/redirecter.cpp b/nucleus/library/application/redirecter.cpp new file mode 100644 index 00000000..78091446 --- /dev/null +++ b/nucleus/library/application/redirecter.cpp @@ -0,0 +1,506 @@ +/*****************************************************************************\ +* * +* Name : stdio_redirecter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "redirecter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __UNIX__ + #include + #include +#endif + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace processes; +using namespace textual; + +namespace application { + +const int IO_PAUSE_PERIOD = 50; // sleep for this long between read attempts. + +const int BUFFER_SIZE = 4096; // maximum we will read at once. + +#undef LOG +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) + +const char *REDIRECTER_INI = "redirecter.ini"; + // used to report process ids, since there are users that need this + // info currently. + +const char *PROCESS_SECTION = "process_id"; + // the section in the ini file where we store our process ids. +//hmmm: above should be removed and pushed into stdio wrapper. + +////////////// + +class reader_thread : public ethread +{ +public: + reader_thread(stdio_redirecter &parent, bool is_stdout) + : ethread(), _is_stdout(is_stdout), _parent(parent) { + } + + virtual ~reader_thread() { + } + + virtual void perform_activity(void *formal(ptr)) { + while (!should_stop()) { + _parent.std_thread_action(_is_stdout); + } + } + +private: + bool _is_stdout; // if true, then stdout, if false, then stderr. + stdio_redirecter &_parent; +}; + +////////////// + +stdio_redirecter::stdio_redirecter() +: +#ifdef __WIN32__ + _child_in(NIL), _child_out(NIL), _child_err(NIL), + _parent_in(NIL), _parent_out(NIL), _parent_err(NIL), + _app_handle(NIL), +#endif + _command(new astring), + _parms(new astring), + _persistent_result(OKAY), + _stdout_reader(new reader_thread(*this, true)), + _stderr_reader(new reader_thread(*this, false)), + _stdout_queue(new byte_array), + _stderr_queue(new byte_array), + _lock(new mutex), + _process_id(0), + _exit_value(0) +{ +} + +stdio_redirecter::stdio_redirecter(const astring &command, + const astring ¶meters) +: +#ifdef __WIN32__ + _child_in(NIL), _child_out(NIL), _child_err(NIL), + _parent_in(NIL), _parent_out(NIL), _parent_err(NIL), + _app_handle(NIL), +#endif + _command(new astring(command)), + _parms(new astring(parameters)), + _persistent_result(OKAY), + _stdout_reader(new reader_thread(*this, true)), + _stderr_reader(new reader_thread(*this, false)), + _stdout_queue(new byte_array), + _stderr_queue(new byte_array), + _lock(new mutex), + _process_id(0), + _exit_value(0) +{ + outcome ret = create_pipes(); + if (ret != OKAY) { _persistent_result = ret; return; } + ret = launch_program(_process_id); + if (ret != OKAY) { _persistent_result = ret; return; } +} + +stdio_redirecter::~stdio_redirecter() +{ + zap_program(); + _process_id = 0; + + WHACK(_stdout_queue); + WHACK(_stderr_queue); + WHACK(_stdout_reader); + WHACK(_stderr_reader); + WHACK(_command); + WHACK(_parms); + WHACK(_lock); +} + +outcome stdio_redirecter::reset(const astring &command, + const astring ¶meters) +{ + zap_program(); + _process_id = 0; + + *_command = command; + *_parms = parameters; + _persistent_result = OKAY; + + outcome ret = create_pipes(); + if (ret != OKAY) { _persistent_result = ret; return ret; } + ret = launch_program(_process_id); + if (ret != OKAY) { _persistent_result = ret; return ret; } + return ret; +} + +outcome stdio_redirecter::create_pipes() +{ + FUNCDEF("create_pipes"); +#ifdef __UNIX__ + // the input and output here are from the perspective of the parent + // process and not the launched program. + if (pipe(_input_fds)) { + LOG("failure to open an unnamed pipe for input."); + return ACCESS_DENIED; + } + if (pipe(_output_fds)) { + LOG("failure to open an unnamed pipe for output."); + return ACCESS_DENIED; + } + if (pipe(_stderr_fds)) { + LOG("failure to open an unnamed pipe for stderr."); + return ACCESS_DENIED; + } +#elif defined (__WIN32__) + // set up the security attributes structure that governs how the child + // process is created. + SECURITY_ATTRIBUTES sa; + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength= sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NIL; + sa.bInheritHandle = true; + + HANDLE in_temp = NIL, out_temp = NIL, err_temp = NIL; + + // create pipes that we will hook up to the child process. these are + // currently inheritable based on the security attributes. + if (!CreatePipe(&_child_in, &in_temp, &sa, 0)) return ACCESS_DENIED; + if (!CreatePipe(&out_temp, &_child_out, &sa, 0)) return ACCESS_DENIED; + if (!CreatePipe(&err_temp, &_child_err, &sa, 0)) return ACCESS_DENIED; + + HANDLE process_handle = GetCurrentProcess(); + // retrieve process handle for use in system calls below. since it's + // a pseudo handle, we don't need to close it. + + // create new handles for the parent process (connected to this object) to + // use. the false indicates that the child should not inherit the properties + // on these because otherwise it cannot close them. + if (!DuplicateHandle(process_handle, in_temp, process_handle, &_parent_in, + 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; + if (!DuplicateHandle(process_handle, out_temp, process_handle, &_parent_out, + 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; + if (!DuplicateHandle(process_handle, err_temp, process_handle, &_parent_err, + 0, false, DUPLICATE_SAME_ACCESS)) return ACCESS_DENIED; + + // close out the handles that we're done with and don't want the child to + // inherit. + CloseHandle(in_temp); + CloseHandle(out_temp); + CloseHandle(err_temp); +#endif + + return OKAY; +} + +outcome stdio_redirecter::launch_program(int &new_process_id) +{ +// FUNCDEF("launch_program"); + new_process_id = 0; +#ifdef __UNIX__ + int fork_ret = fork(); + if (fork_ret == 0) { + // this is the child. + close(_output_fds[1]); // close our *input* pipe's output fd. + dup2(_output_fds[0], 0); // close our stdin and replace with input pipe. + close(_input_fds[0]); // close our *output* pipe's input fd. + dup2(_input_fds[1], 1); // close our stdout and replace with output pipe. + close(_stderr_fds[0]); // close stderr input fd. + dup2(_stderr_fds[1], 2); // close our stderr and pipe it to parent. + // now we want to launch the program for real. + processes::char_star_array parms = launch_process::break_line(*_command, *_parms); + execv(_command->s(), parms.observe()); + // oops. failed to exec if we got to here. + exit(1); + } else { + // this is the parent. + _process_id = fork_ret; // save the child's process id. + new_process_id = _process_id; // set the returned id. + close(_output_fds[0]); // close our *output* pipe's input fd. + close(_input_fds[1]); // close our *input* pipe's output fd. + close(_stderr_fds[1]); // close the child's stderr output side. + // now we should have a set of pipes that talk to the child. + } +#elif defined (__WIN32__) + // set up the startup info struct. + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.hStdInput = _child_in; + si.hStdOutput = _child_out; + si.hStdError = _child_err; + si.wShowWindow = SW_HIDE; // we'll hide the console window. + + // setup the security attributes for the new process. + SECURITY_DESCRIPTOR *sec_desc = (SECURITY_DESCRIPTOR *)GlobalAlloc + (GPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + InitializeSecurityDescriptor(sec_desc, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(sec_desc, -1, 0, 0); + LPSECURITY_ATTRIBUTES sec_attr = (LPSECURITY_ATTRIBUTES)GlobalAlloc(GPTR, + sizeof(SECURITY_ATTRIBUTES)); + sec_attr->nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr->lpSecurityDescriptor = sec_desc; + sec_attr->bInheritHandle = true; + + astring cmd = *_command; + if (cmd[0] != '"') + cmd.insert(0, "\""); + if (cmd[cmd.end()] != '"') + cmd += "\""; + cmd += " "; + cmd += *_parms; + + // fork off the process. + PROCESS_INFORMATION pi; + BOOL success = CreateProcess(NIL, to_unicode_temp(cmd), sec_attr, NIL, + true, CREATE_NEW_CONSOLE, NIL, NIL, &si, &pi); + + // cleanup junk we allocated. + if (sec_attr != NIL) GlobalFree(sec_attr); + if (sec_desc != NIL) GlobalFree(sec_desc); + + if (success) { + // toss out the thread handle since we don't use it. + CloseHandle(pi.hThread); + // track the important handle, for our application. + _app_handle = pi.hProcess; +//hmmm: boot this stuff out into the stdio_wrapper class, which is the only +// thing that should do this. + ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY); + ini.store(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id()), + a_sprintf("%d", pi.dwProcessId)); + _process_id = pi.dwProcessId; + new_process_id = _process_id; + } else { + return NOT_FOUND; + } +#endif + + _stdout_reader->start(NIL); + _stderr_reader->start(NIL); + + return OKAY; +} + +bool stdio_redirecter::running() +{ + if (!_process_id) return false; // nothing to check. + if (_exit_value != 0) return false; // gone by now. +#ifdef __UNIX__ + int status; + pid_t pid = waitpid(_process_id, &status, WNOHANG); + if (!pid) return true; // still going. + if (!_exit_value) { +//hmmm: is that all we need from it? unprocessed exit value? + _exit_value = status; + } + if (WIFEXITED(status)) { + // the child exited on its own. + _process_id = 0; + return false; + } else if (WIFSIGNALED(status)) { + // the child was zapped by a signal. + _process_id = 0; + return false; + } + return true; +#elif defined (__WIN32__) + DWORD exit_value = 0; + // see if there's an exit code yet. if this fails with false, then the + // process is maybe long gone or something? + BOOL ret = GetExitCodeProcess(_app_handle, &exit_value); + if (ret) { + // store it if we had no previous version. + if (exit_value != STILL_ACTIVE) { + _exit_value = exit_value; + return false; + } + return true; + } else { + // this one seems to still be going. + return true; + } +#endif +} + +void stdio_redirecter::close_input() +{ +#ifdef __UNIX__ + close(_output_fds[1]); // shut down input to the child program. +#elif defined(__WIN32__) + if (_child_in) { CloseHandle(_child_in); _child_in = NIL; } + if (_parent_in) { CloseHandle(_parent_in); _parent_in = NIL; } +#endif +} + +void stdio_redirecter::zap_program() +{ + FUNCDEF("zap_program"); + _stdout_reader->cancel(); + _stderr_reader->cancel(); + +#ifdef __UNIX__ + close_input(); + close(_stderr_fds[0]); + close(_input_fds[0]); + + if (_process_id) { + kill(_process_id, 9); // end the program without any doubt. + } + _process_id = 0; +#elif defined(__WIN32__) + if (_app_handle) { + // none of the handle closing works if the app is still running. + // microsoft hasn't really got a clue, if you cannot close a file handle + // when you want to, but that's apparently what's happening. + TerminateProcess(_app_handle, 1); + } + + close_input(); + + if (_child_out) { CloseHandle(_child_out); _child_out = NIL; } + if (_parent_out) { CloseHandle(_parent_out); _parent_out = NIL; } + + if (_child_err) { CloseHandle(_child_err); _child_err = NIL; } + if (_parent_err) { CloseHandle(_parent_err); _parent_err = NIL; } + + // shut down the child process if it's still there. + if (_app_handle) { + DWORD ret; + +//hmmm: also should only be in the stdio wrapper program. +//hmmm: remove this in favor of the stdio wrapper or whomever tracking their +// own process id. + ini_configurator ini(REDIRECTER_INI, ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY); + ini.delete_entry(PROCESS_SECTION, a_sprintf("%d", application_configuration::process_id())); + + GetExitCodeProcess(_app_handle, &ret); + if (ret == STILL_ACTIVE) { + // it's still bumbling along; let's drop it. + TerminateProcess(_app_handle, 1); + if (WaitForSingleObject(_app_handle, 1000) == WAIT_TIMEOUT) { +///problem! + LOG("hmmm, we timed out waiting for the process to exit."); + } + } + CloseHandle(_app_handle); + _app_handle = NIL; + } +#endif + + _stdout_reader->stop(); + _stderr_reader->stop(); +} + +outcome stdio_redirecter::read(byte_array &received) +{ + received.reset(); + if (_persistent_result != OKAY) return _persistent_result; + auto_synchronizer l(*_lock); + if (!_stdout_queue->length()) return NONE_READY; +//hmmm: signal eof too! + received = *_stdout_queue; + _stdout_queue->reset(); + return common::OKAY; +} + +outcome stdio_redirecter::write(const astring &to_write, int &written) +{ + byte_array real_write(to_write.length(), (abyte *)to_write.observe()); + return write(real_write, written); +} + +outcome stdio_redirecter::write(const byte_array &to_write, int &written) +{ +// FUNCDEF("write"); + written = 0; + if (_persistent_result != OKAY) return _persistent_result; +#ifdef __UNIX__ + int writ = ::write(_output_fds[1], to_write.observe(), to_write.length()); + if (writ < 0) return ACCESS_DENIED; + written = writ; + return OKAY; +#elif defined(__WIN32__) + DWORD writ = 0; + BOOL ret = WriteFile(_parent_in, to_write.observe(), to_write.length(), + &writ, NIL); + written = writ; + if (ret) return OKAY; + else return ACCESS_DENIED; +#endif +} + +outcome stdio_redirecter::read_stderr(byte_array &received) +{ + received.reset(); + if (_persistent_result != OKAY) return _persistent_result; + auto_synchronizer l(*_lock); + if (!_stderr_queue->length()) return NONE_READY; +//signal eof too! + received = *_stderr_queue; + _stderr_queue->reset(); + return common::OKAY; +} + +void stdio_redirecter::std_thread_action(bool is_stdout) +{ +// FUNCDEF("std_thread_action"); + byte_array buff(BUFFER_SIZE + 1); +#ifdef __UNIX__ + bool ret = false; + int fd = _input_fds[0]; + if (!is_stdout) fd = _stderr_fds[0]; + if (!fd) return; // nothing to read from. + int bytes_read = ::read(fd, buff.access(), BUFFER_SIZE); + if (!bytes_read) { +//indicates end of file; set flags! + } else if (bytes_read > 0) { + ret = true; // there's new data in our buffer. + } +#elif defined(__WIN32__) + HANDLE where = _parent_out; + if (!is_stdout) where = _parent_err; + if (!where) return; // nothing to read from. + // read some data from the file. the function will return when a write + // operation completes or we get that much data. + DWORD bytes_read = 0; + BOOL ret = ReadFile(where, buff.access(), BUFFER_SIZE, &bytes_read, NIL); +//hmmm: if (ret && !bytes_read) {///set eof!!! } +#endif + if (ret && bytes_read) { + auto_synchronizer l(*_lock); + byte_array *queue = _stdout_queue; + if (!is_stdout) queue = _stderr_queue; + *queue += buff.subarray(0, bytes_read - 1); + } +} + +} //namespace. + + diff --git a/nucleus/library/application/redirecter.h b/nucleus/library/application/redirecter.h new file mode 100644 index 00000000..3f52f263 --- /dev/null +++ b/nucleus/library/application/redirecter.h @@ -0,0 +1,138 @@ +#ifndef STDIO_REDIRECTER_CLASS +#define STDIO_REDIRECTER_CLASS + +/*****************************************************************************\ +* * +* Name : stdio_redirecter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace application { + +//! Redirects I/O for a newly launched application. + +class stdio_redirecter : public virtual basis::root_object +{ +public: + stdio_redirecter(const basis::astring &command, const basis::astring ¶meters); + //!< controls the I/O for a program started with "command" and "parameters". + /*!< this creates an io redirecter that will trap the inputs and outputs + of a program called "command" that is launched with the "parameters". + the program can be communicated with via the read and write methods + below. */ + + stdio_redirecter(); + //!< creates a blank redirecter which should be started up with reset(). + + ~stdio_redirecter(); + //!< shuts down the program that was launched, if it's still running. + + basis::outcome reset(const basis::astring &command, const basis::astring ¶meters); + //!< shuts down the active program and starts with new parameters. + + DEFINE_CLASS_NAME("stdio_redirecter"); + + enum outcomes { + OKAY = basis::common::OKAY, + NOT_FOUND = basis::common::NOT_FOUND, //!< the file to launch was not found. + NONE_READY = basis::common::NONE_READY, //!< there was no data available. + PARTIAL = basis::common::PARTIAL, //!< only some of the bytes could be stored. + ACCESS_DENIED = basis::common::ACCESS_DENIED //!< the OS told us we could not. + }; + + basis::outcome health() const { return _persistent_result; } + //!< if the object constructed successfully, this returns OKAY. + /*!< otherwise an error occurred during the pipe creation or process fork. + */ + + bool running(); + //!< returns true if the application is still running. + /*!< if it exited on its own, then this will return false. */ + + int exit_value() const { return _exit_value; } + //!< returns the exit value from the app after it was launched. + /*!< this is only valid if the launch succeeded, the app ran, and now the + running() method is returning false because the application exited. */ + + int process_id() const { return _process_id; } + //!< the process id of the launched application. + /*!< this is only valid if the app is still running. */ + + void zap_program(); + //!< attempts to force a shutdown of the launched application. + /*!< this also closes out all of our pipes and handles. */ + + basis::outcome read(basis::byte_array &received); + //!< attempts to read bytes from the program's standard output. + /*!< if any bytes were found, OKAY is returned and the bytes are stored + in "received". */ + + basis::outcome write(const basis::byte_array &to_write, int &written); + //!< writes the bytes in "to_write" into the program's standard input. + /*!< OKAY is returned if all were successfully written. the number of + bytes that were actually sent to the program is put in "written". if only + a portion of the bytes could be written, then PARTIAL is returned. */ + + basis::outcome write(const basis::astring &to_write, int &written); + //!< sends a string "to_write" to the launched program's standard input. + + basis::outcome read_stderr(basis::byte_array &received); + //!< reads from the program's standard error stream similarly to read(). + + void close_input(); + //!< shuts down the standard input to the program. + /*!< this simulates when the program receives an end of file on its + main input. */ + + // internal use only... + + void std_thread_action(bool is_stdout); + //!< invoked by our threads when data becomes available. + /*!< if "is_stdout" is true, then that's the pipe we get data from. + otherwise we will try to get data from stderr. */ + +private: +#ifdef __UNIX__ + int _output_fds[2]; //!< file descriptors for parent's output. + int _input_fds[2]; //!< file descriptors for parent's input. + int _stderr_fds[2]; //!< file descriptors for standard error from child. +#endif +#ifdef __WIN32__ + void *_child_in, *_child_out, *_child_err; //!< child process pipes replace io. + void *_parent_in, *_parent_out, *_parent_err; //!< our access to the pipes. + void *_app_handle; //!< refers to the launched application. +#endif + basis::astring *_command; //!< the application that we'll start and switch I/O on. + basis::astring *_parms; //!< the parameters for the app we will launch. + basis::outcome _persistent_result; //!< this records failures during construction. + processes::ethread *_stdout_reader; //!< gets data from process's stdout. + processes::ethread *_stderr_reader; //!< gets data from process's stderr. + basis::byte_array *_stdout_queue; //!< holds data acquired from stdout. + basis::byte_array *_stderr_queue; //!< holds data acquired from stderr. + basis::mutex *_lock; //!< protects our queues. + int _process_id; //!< pid for the launched program. + int _exit_value; //!< stored once when we notice app is gone. + + basis::outcome create_pipes(); + //!< creates the three pipes that the child will be given as stdin, stdout and stderr. + + basis::outcome launch_program(int &new_process_id); + //!< launches the application using the previously established pipes. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/application/shared_memory.cpp b/nucleus/library/application/shared_memory.cpp new file mode 100644 index 00000000..4dab6191 --- /dev/null +++ b/nucleus/library/application/shared_memory.cpp @@ -0,0 +1,216 @@ +/*****************************************************************************\ +* * +* Name : shared_memory * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "shared_memory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include + #include + #include + #include + #include + #include +#endif + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; + +namespace application { + +shared_memory::shared_memory(int size, const char *identity) +: _locking(new rendezvous(identity)), + _the_memory(NIL), + _valid(false), + _identity(new astring(identity)), + _size(size) +{ +// FUNCDEF("constructor"); + bool first_use = false; // assume already existing until told otherwise. + _locking->lock(); // force all others to wait on our finishing creation. +#ifdef __UNIX__ + int flag = O_RDWR | O_CREAT | O_EXCL; + // try to create the object if it doesn't exist yet, but fail if it does. + int mode = 0700; // rwx------ for just us. + _the_memory = shm_open(special_filename(identity).s(), flag, mode); + // create the shared memory object but fail if it already exists. + if (negative(_the_memory)) { + // failed to create the shared segment. try without forcing exclusivity. + flag = O_RDWR | O_CREAT; + _the_memory = shm_open(special_filename(identity).s(), flag, mode); + basis::un_int err = critical_events::system_error(); // get last error. + if (negative(_the_memory)) { + // definitely a failure now... + printf("error allocating shared segment for %s, was told %s.\n", + special_filename(identity).s(), critical_events::system_error_text(err).s()); + _the_memory = 0; + _locking->unlock(); // release lock before return. + return; + } + // getting to here means the memory was already allocated. so we're fine. + } else { + // the shared memory segment was just created this time. + int ret = ftruncate(_the_memory, size); + basis::un_int err = critical_events::system_error(); // get last error. + if (ret) { + printf("error truncating shared segment for %s, was told %s.", + special_filename(identity).s(), critical_events::system_error_text(err).s()); + } + first_use = true; + } + _valid = true; +#elif defined(__WIN32__) + _the_memory = ::CreateFileMapping((HANDLE)-1, NULL, PAGE_READWRITE, + 0, size, to_unicode_temp(identity)); + basis::un_int err = critical_events::system_error(); // get last error. + first_use = (err != ERROR_ALREADY_EXISTS); + if (!_the_memory) { + _locking->unlock(); + return; // not healthy. + } + _valid = true; +#else +//this is junk; simulates shared memory poorly. + #pragma message("simulating shared memory since unknown for this platform.") + if (!_bogus_shared_space().length()) { + _bogus_shared_space().reset(size); + first_use = true; + } + _the_memory = _bogus_shared_space().access(); + _valid = true; +#endif + if (first_use) { + // initialize the new memory to all zeros. + abyte *contents = locked_grab_memory(); + if (!contents) { + _valid = false; + _locking->unlock(); + return; + } + memset(contents, 0, size); + locked_release_memory(contents); // release the memory for now. + } + _locking->unlock(); +} + +shared_memory::~shared_memory() +{ +#ifdef __UNIX__ + if (_the_memory) { + close(int(_the_memory)); + shm_unlink(special_filename(identity()).s()); + } +#elif defined(__WIN32__) + ::CloseHandle(_the_memory); +#else + //hmmm: fix. + _the_memory = NIL; +#endif + WHACK(_locking); + WHACK(_identity); + _valid = false; +} + +const astring &shared_memory::identity() const { return *_identity; } + +astring shared_memory::special_filename(const astring &identity) +{ + astring shared_file = identity; + filename::detooth_filename(shared_file); + shared_file = astring("/tmp_") + "sharmem_" + shared_file; + return shared_file; +} + +astring shared_memory::unique_shared_mem_identifier(int sequencer) +{ + astring to_return("SMID-"); + to_return += a_sprintf("%d-%d-", application_configuration::process_id(), sequencer); + to_return += application_configuration::application_name(); + // replace file delimiters in the name with a safer character. + filename::detooth_filename(to_return); + return to_return; +} + +bool shared_memory::first_usage(abyte *contents, int max_compare) +{ + for (int i = 0; i < max_compare; i++) + if (contents[i] != 0) return false; + return true; +} + +abyte *shared_memory::lock() +{ + _locking->lock(); + return locked_grab_memory(); +} + +void shared_memory::unlock(abyte * &to_unlock) +{ + locked_release_memory(to_unlock); + _locking->unlock(); +} + +abyte *shared_memory::locked_grab_memory() +{ + FUNCDEF("locked_grab_memory") + abyte *to_return = NIL; + if (!_the_memory) return to_return; +#ifdef __UNIX__ + to_return = (abyte *)mmap(NIL, _size, PROT_READ | PROT_WRITE, + MAP_SHARED, int(_the_memory), 0); +#elif defined(__WIN32__) + to_return = (abyte *)::MapViewOfFile((HANDLE)_the_memory, FILE_MAP_ALL_ACCESS, + 0, 0, 0); +#else + to_return = (abyte *)_the_memory; +#endif + if (!to_return) { +// FUNCTION(func); +//not working yet. callstack tracker or whatever is hosed up. + throw(astring(astring(class_name()) + "::" + func + ": no data was accessible in shared space.")); + } + return to_return; +} + +void shared_memory::locked_release_memory(abyte * &to_unlock) +{ + if (!_the_memory || !to_unlock) return; +#ifdef __UNIX__ + munmap(to_unlock, _size); +#elif defined(__WIN32__) + ::UnmapViewOfFile(to_unlock); +#else +//uhh. +#endif +} + +} //namespace. + diff --git a/nucleus/library/application/shared_memory.h b/nucleus/library/application/shared_memory.h new file mode 100644 index 00000000..47c8a61c --- /dev/null +++ b/nucleus/library/application/shared_memory.h @@ -0,0 +1,110 @@ +#ifndef SHARED_MEMORY_CLASS +#define SHARED_MEMORY_CLASS + +/*****************************************************************************\ +* * +* Name : shared_memory * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace application { + +//! Implements storage for memory that can be shared between threads. +/*! + Provides a means to create shared memory chunks and access them from + anywhere in a program or from cooperating programs. +*/ + +class shared_memory : public virtual basis::root_object +{ +public: + shared_memory(int size, const char *identity); + //!< a shared chunk of the "size" specified will be created. + /*!< it is named by the "identity" string. that "identity" uniquely + points to one shared chunk on this machine. note that if this object is + the first to create the chunk of memory, then all of the contents are + initialized to zero. this can be used to determine if the chunk needs + higher level, application-specific initialization. */ + + virtual ~shared_memory(); + //!< cleans up the shared bit of memory as far as we're concerned. + /*!< if some other instance still has it opened, then it isn't + destroyed for real yet. */ + + DEFINE_CLASS_NAME("shared_memory"); + + bool valid() const { return _valid; } + //!< this must be true for the shared_memory to be usable. + /*!< if it's false, then the memory chunk was never created. */ + + int size() const { return _size; } + //!< returns the size of the shared chunk of memory. + + const basis::astring &identity() const; + //!< provides a peek at the name that this chunk was constructed with. + + bool first_usage(basis::abyte *locked_memory, int max_compare); + //!< returns true if the "locked_memory" was just created. + /*!< that is assuming that a user of the shared memory will set the first + "max_compare" bytes to something other than all zeros. this is really + just a test of whether bytes zero through bytes "max_compare" - 1 are + currently zero, causing a return of true. seeing anything besides zero + causes a false return. */ + + basis::abyte *lock(); + //!< locks the shared memory and returns a pointer to the storage. + /*!< the synchronization supported is only within this program; this type + of shared memory is not intended for access from multiple processes, + just for access from multiple threads in the same app. */ + + void unlock(basis::abyte * &to_unlock); + //!< returns control of the shared memory so others can access it. + /*!< calls to lock() must be paired up with calls to unlock(). */ + + static basis::astring unique_shared_mem_identifier(int sequencer); + //!< returns a unique identifier for a shared memory chunk. + /*!< the id returned is unique on this host for this particular process + and application, given a "sequencer" number that is up to the application + to keep track of uniquely. the values from -100 through -1 are reserved + for hoople library internals. */ + +private: + processes::rendezvous *_locking; //!< protects our shared memory. +#ifdef __UNIX__ + int _the_memory; //!< OS index of the memory. +#elif defined(__WIN32__) + void *_the_memory; //!< OS pointer to the memory. +#endif + bool _valid; //!< true if the memory creation succeeded. + basis::astring *_identity; //!< holds the name we were created with. + int _size; //!< size of memory chunk. + + // these do the actual work of getting the memory. + basis::abyte *locked_grab_memory(); + void locked_release_memory(basis::abyte * &to_unlock); + + static basis::astring special_filename(const basis::astring &identity); + //!< provides the name for our shared memory file, if needed. + + // forbidden. + shared_memory(const shared_memory &); + shared_memory &operator =(const shared_memory &); +}; + +} //namespace. + + +#endif // outer guard. + diff --git a/nucleus/library/application/singleton_application.cpp b/nucleus/library/application/singleton_application.cpp new file mode 100644 index 00000000..14b3fa6e --- /dev/null +++ b/nucleus/library/application/singleton_application.cpp @@ -0,0 +1,69 @@ +/*****************************************************************************\ +* * +* Name : singleton_application * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "singleton_application.h" + +#include +#include + +using namespace basis; +using namespace filesystem; +using namespace processes; + +namespace application { + +singleton_application::singleton_application(const astring &application_name, + const astring &user_name) +: c_initial_try(0), + _app_lock(new rendezvous(filename(application_name).basename().raw() + + "__" + user_name)), + _got_lock(false) +{ +} + +singleton_application::~singleton_application() +{ + release_lock(); + WHACK(_app_lock); +} + +bool singleton_application::already_running() +{ return !allow_this_instance(); } + +bool singleton_application::already_tried() const +{ return c_initial_try > 0; } + +void singleton_application::release_lock() +{ + if (_got_lock) { + _app_lock->unlock(); + _got_lock = false; + } +} + +bool singleton_application::allow_this_instance() +{ + if (_got_lock) return true; // already grabbed it. + + if (!already_tried()) { + _got_lock = _app_lock->lock(rendezvous::IMMEDIATE_RETURN); + if (_got_lock) c_initial_try = 1; // succeeded in locking. + else c_initial_try = 2; // failure. + } + + return _got_lock; +} + +} //namespace. + diff --git a/nucleus/library/application/singleton_application.h b/nucleus/library/application/singleton_application.h new file mode 100644 index 00000000..f086809e --- /dev/null +++ b/nucleus/library/application/singleton_application.h @@ -0,0 +1,75 @@ +#ifndef SINGLETON_APPLICATION_CLASS +#define SINGLETON_APPLICATION_CLASS + +/*****************************************************************************\ +* * +* Name : singleton_application * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! Implements an application lock to ensure only one is running at once. +/*! + This encapsulates the code used to ensure that only one copy of an + application is running at a time. It can either be made specific to a + user (so that user can run only one at a time) or made global to the entire + machine. +*/ + +#include +#include +#include + +namespace application { + +class singleton_application +{ +public: + singleton_application(const basis::astring &application_name, + const basis::astring &user_name = basis::astring::empty_string()); + //!< constructs a singleton guardian for the "application_name". + /*!< if the "user_name" is non-empty, then it will be used as part of + the lock name. this ensures that separate users can still run the + application at the same time, which is especially important for terminal + servers. for a lock that should affect the whole machine and ignore + users, the "user_name" must be empty. */ + + virtual ~singleton_application(); + + DEFINE_CLASS_NAME("singleton_application"); + + bool already_tried() const; + //!< returns true if the singleton has already tried to lock. + + bool allow_this_instance(); + //!< the application must check this before starting up. + /*!< if this returns false, then the application must exit immediately or + it will be violating the singleton rule. */ + + bool already_running(); + //!< returns false if this program is not already running. + /*!< this is just the opposite of the allow_this_instance() method. */ + + void release_lock(); + //!< let's go of the application lock, if we had previously gotten it. + /*!< this should only be called when the application is about to exit. */ + +private: + int c_initial_try; //!< has the initial locking attempt been made? + /* if c_initial_try is zero, no attempt made yet. if it's 1, then tried + and succeeded. if it's greater than one, then tried and failed. */ + processes::rendezvous *_app_lock; //!< used to lock out other instances. + bool _got_lock; //!< records whether we successfully got the lock or not. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/application/window_classist.h b/nucleus/library/application/window_classist.h new file mode 100644 index 00000000..5b9a1ccb --- /dev/null +++ b/nucleus/library/application/window_classist.h @@ -0,0 +1,147 @@ +#ifndef WINDOW_CLASSIST_CLASS +#define WINDOW_CLASSIST_CLASS + +/*****************************************************************************\ +* * +* Name : window_classist * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! An add-in file providing window class registration and a window procedure. +/*! + This file makes it easier to add a very simple window to any console or + win32 application that might need it (possibly because the app does not + create any windows itself, but for crazy insane reasons, a window is still + needed by an external agent, ahem installshield). It implements a very + important part of this process, which is setting a window procedure and + registering a window class. Sometime in 2005 or 2006, a windows update + came through that made these formerly optional practices mandatory (and + broke many of our applications that created windows without a window + procedure or class registration). That occurrence prompted the creation + of this class which tries to provide the bare minimum needed to make + things work again. + + Example Usage: + + // include our code file to embed the window procedure and register class + // methods in whoever needs them. this should only be needed once per + // program. + + #include + + // create our simple window... + + basis::astring window_title = "my_freaky_window"; + basis::astring class_name = "jumbo_stompy_update_crudburger"; + + window_handle f_window = create_simplistic_window(window_title, class_name); + + // and then much later, after the window is no longer needed... + + whack_simplistic_window(f_window); + +*/ + +#include "windoze_helper.h" + +#include + +namespace application { + +#ifndef __WIN32__ + +// this is a placeholder implementation for other platforms. +window_handle create_simplistic_window(const basis::astring &formal(window_title), + const basis::astring &formal(class_name)) { return NIL; } +void whack_simplistic_window(window_handle formal(f_window)) {} + +#else + +//! this is the very simple window procedure used by register_class() below. + +LRESULT CALLBACK window_procedure(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + + switch (message) { + case WM_COMMAND: { + int identifier, event; + identifier = LOWORD(wParam); + event = HIWORD(wParam); + return DefWindowProc(hWnd, message, wParam, lParam); + break; + } + case WM_PAINT: { + HDC hdc; + PAINTSTRUCT ps; + hdc = BeginPaint(hWnd, &ps); + // hmmm: Add any drawing code here... + EndPaint(hWnd, &ps); + break; + } + case WM_DESTROY: { + PostQuitMessage(0); + break; + } + default: { + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + return 0; +} + +//! returns the registered class as a windows atom. + +ATOM register_class(const basis::astring &name) +{ + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = (WNDPROC)window_procedure; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GET_INSTANCE_HANDLE(); + wcex.hIcon = 0; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + basis::to_unicode_persist(temp_class, name); + wcex.lpszClassName = temp_class; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex); +} + +window_handle create_simplistic_window(const basis::astring &window_title, + const basis::astring &class_name) +{ + register_class(class_name); + window_handle f_window = CreateWindow(basis::to_unicode_temp(class_name), + basis::to_unicode_temp(window_title), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + 0, CW_USEDEFAULT, 0, NIL, NIL, GET_INSTANCE_HANDLE(), NIL); + ShowWindow(f_window, SW_HIDE); + UpdateWindow(f_window); + return f_window; +} + +void whack_simplistic_window(window_handle f_window) +{ + SendMessage(f_window, WM_CLOSE, NIL, NIL); +//hmmm: is this enough? +} + +#endif // win32 + +} // namespace. + +#endif // outer guard + diff --git a/nucleus/library/application/windoze_helper.cpp b/nucleus/library/application/windoze_helper.cpp new file mode 100644 index 00000000..c9c29109 --- /dev/null +++ b/nucleus/library/application/windoze_helper.cpp @@ -0,0 +1,934 @@ +/*****************************************************************************\ +* * +* Name : windoze_helper * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "windoze_helper.h" +///#include "array.h" +///#include "build_configuration.h" +///#include "convert_utf.h" +///#include "function.h" +///#include "mutex.h" +///#include "portable.h" +///#include "set.h" +///#include "version_record.h" +#include + +///#include +///#include + +/* +#ifdef __UNIX__ + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif +#ifdef __XWINDOWS__ +//hmmm: need code for the wait cursor stuff. +#endif +#ifdef __WIN32__ + #include + #include + #include + #include +#endif +*/ + +using namespace basis; +using namespace structures; +using namespace configuration; + +#undef static_class_name +#define static_class_name() "windoze_helper" + +/* +//#define DEBUG_PORTABLE + // uncomment for noisy debugging. + +//#define DEBUG_UPTIME + // uncomment to get noisier reporting about system and rolling uptime. + +//#define JUMP_TIME_49_DAYS + // uncomment to make our uptimes start just before the 32 bit uptime rollover. +//#define JUMP_TIME_497_DAYS + // uncomment to make our uptimes start just before the jiffies rollover. + +//#define ENABLE_ROLLOVER_BUG + // uncomment to get old behavior back where the uptime was not rolling + // over properly. this turns off our remainder calculation and leaves the + // conversion as a simple cast, which will fail and get stuck at 2^32-1. + +#define SUPPORT_SHELL_EXECUTE + // if this is not commented out, then the ShellExecute version of launch_ + // -process() is available. when commented out, ShellExecute is turned off. + // disabling this support is the most common because the ShellExecute method + // in win32 was only supported for wk203 and wxp, that is only after + // windows2000 was already available. since nt and w2k don't support this, + // we just usually don't mess with it. it didn't answer a single one of our + // issues on windows vista (wfista) anyway, so it's not helping. + +// ensure we always have debugging turned on if the jump is enabled. +#ifdef JUMP_TIME_49_DAYS + #undef DEBUG_UPTIME + #define DEBUG_UPTIME +#endif +#ifdef JUMP_TIME_497_DAYS + #undef DEBUG_UPTIME + #define DEBUG_UPTIME +#endif +// the JUMP..DAYS macros are mutually exclusive. use none or one, not both. +#ifdef JUMP_TIME_497_DAYS + #ifdef JUMP_TIME_49_DAYS + #error One cannot use both 497 day and 49 day bug inducers + #endif +#endif +#ifdef JUMP_TIME_49_DAYS + #ifdef JUMP_TIME_497_DAYS + #error One cannot use both 49 day and 497 day bug inducers + #endif +#endif +*/ + + +namespace application { + +/* +mutex BASIS_EXTERN &__uptime_synchronizer(); + // used by our low-level uptime methods to protect singleton variables. +mutex BASIS_EXTERN &__process_synchronizer(); + // used for synchronizing our records of child processes. +#ifdef __UNIX__ +int_set BASIS_EXTERN &__our_kids(); + // the static list of processes we've started. +#endif + +const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; + // maximum command line that we'll deal with here. + +#ifdef __UNIX__ + const char *UPTIME_REPORT_FILE = "/tmp/uptime_report.log"; +#endif +#ifdef __WIN32__ + const char *UPTIME_REPORT_FILE = "c:/uptime_report.log"; +#endif + +#undef LOG +#define LOG(s) STAMPED_EMERGENCY_LOG(program_wide_logger(), s); + +#define COMPLAIN(to_print) { \ + guards::write_to_console((isprintf("basis/portable::%s: ", func) + to_print).s()); \ +} + +static const double __rollover_point = 2.0 * double(MAXINT); + // this number is our rollover point for 32 bit integers. + +double rolling_uptime() +{ + auto_synchronizer l(__uptime_synchronizer()); + // protect our rollover records. + + static u_int __last_ticks = 0; + static int __rollovers = 0; + + u_int ticks_up = system_uptime(); + // acquire the current uptime as a 32 bit unsigned int. + + if (ticks_up < __last_ticks) { + // rollover happened. increment our tracker. + __rollovers++; + } + __last_ticks = ticks_up; + + double to_return = double(__rollovers) * __rollover_point + double(ticks_up); + +#ifdef DEBUG_UPTIME + #ifdef __WIN32__ + static FILE *__outfile = fopen(UPTIME_REPORT_FILE, "a+b"); + static double old_value = 0; + if (absolute_value(old_value - to_return) > 9.9999) { + // only report when the time changes by more than 10 ms. + fprintf(__outfile, "-> uptime=%.0f\n", to_return); + fflush(__outfile); + old_value = to_return; + if (__rollover_point - to_return <= 40.00001) { + fprintf(__outfile, "---> MAXIMUM UPTIME SOON!\n"); + fflush(__outfile); + } + } + #endif +#endif + + return to_return; +} + +u_int system_uptime() +{ +#ifdef __WIN32__ + return timeGetTime(); +#else + auto_synchronizer l(__uptime_synchronizer()); + + static clock_t __ctps = sysconf(_SC_CLK_TCK); // clock ticks per second. + static const double __multiplier = 1000.0 / double(__ctps); + // the multiplier gives us our full range for the tick counter. + +#ifdef DEBUG_UPTIME + static FILE *__outfile = fopen(UPTIME_REPORT_FILE, "wb"); + if (__multiplier - u_int(__multiplier) > 0.000001) { + fprintf(__outfile, "uptime multiplier is " + "non-integral (%f)!\n", __multiplier); + fflush(__outfile); + } +#endif + + // read uptime info from the OS. + tms uptime; + u_int real_ticks = times(&uptime); + +#ifdef JUMP_TIME_497_DAYS + static u_int old_value_497 = 0; + bool report_497 = (absolute_value(real_ticks - old_value_497) > 99); + if (report_497) { + old_value_497 = real_ticks; // update before changing it. + fprintf(__outfile, "pre kludge497 tix=%u\n", real_ticks); + fflush(__outfile); + } + real_ticks += u_int(49.0 * double(DAY_ms) + 17.0 * double(HOUR_ms)); + if (report_497) { + fprintf(__outfile, "post kludge497 tix=%u\n", real_ticks); + fflush(__outfile); + } +#endif + + // now turn this into the number of milliseconds. + double ticks_up = (double)real_ticks; + ticks_up = ticks_up * __multiplier; // convert to time here. + +#ifdef JUMP_TIME_497_DAYS + #ifndef ENABLE_ROLLOVER_BUG + // add in additional time so we don't have to wait forever. we make the + // big number above rollover, but that's still got 27.5 or so minutes before + // we really rollover. so we add that part of the fraction (lost in the + // haze of the multiplier) in here. we don't add this in unless they are + // not exercising the rollover bug, because we already know that the 497 + // day bug will show without the addition. but when we're already fixing + // the uptime, we jump time a bit forward so we only have to wait a couple + // minutes instead of 27. + ticks_up += 25.0 * MINUTE_ms; + #endif +#endif + +#ifdef JUMP_TIME_49_DAYS + static u_int old_value_49 = 0; + bool report_49 = (absolute_value(u_int(ticks_up) - old_value_49) > 999); + if (report_49) { + old_value_49 = u_int(ticks_up); // update before changing it. + fprintf(__outfile, "pre kludge49 up=%f\n", ticks_up); + fflush(__outfile); + } + ticks_up += 49.0 * double(DAY_ms) + 17.0 * double(HOUR_ms); + if (report_49) { + fprintf(__outfile, "post kludge49 up=%f\n", ticks_up); + fflush(__outfile); + } +#endif + +#ifndef ENABLE_ROLLOVER_BUG + // fix the return value if is has actually gone over the 2^32 limit for uint. + // casting a double larger than 2*MAXINT to a u_int on some platforms does + // not calculate a rolled-over value, but instead leaves the int at 2*MAXINT. + // thus we make sure it will be correct, as long as there are no more than + // 2^32-1 rollovers, which would be about 584,542 millenia. it's unlikely + // earth will last that long, so this calculation seems safe. + u_int divided = u_int(ticks_up / __rollover_point); + double to_return = ticks_up - (double(divided) * __rollover_point); +#else + // we use the previous version of this calculation, which expected a u_int + // to double conversion to provide a modulo operation rather than just leaving + // the u_int at its maximum value (2^32-1). however, that expectation is not + // guaranteed on some platforms (e.g., ARM processor with floating point + // emulation) and thus it becomes a bug around 49 days and 17 hours into + // OS uptime because the value gets stuck at 2^32-1 and never rolls over. + double to_return = ticks_up; +#endif + +#ifdef DEBUG_UPTIME + static u_int old_value = 0; + int to_print = int(u_int(to_return)); + if (absolute_value(int(old_value) - to_print) > 9) { + // only report when the time changes by more than 10 ms. + fprintf(__outfile, "-> uptime=%u\n", to_print); + fflush(__outfile); + old_value = u_int(to_print); + if (__rollover_point - to_return <= 40.00001) { + fprintf(__outfile, "---> MAXIMUM UPTIME SOON!\n"); + fflush(__outfile); + } + } +#endif + + return u_int(to_return); +#endif +} + +void sleep_ms(u_int msec) +{ +#ifdef __UNIX__ + usleep(msec * 1000); +#endif +#ifdef __WIN32__ + Sleep(msec); +#endif +} + +istring null_device() +{ +#ifdef __WIN32__ + return "null:"; +#else + return "/dev/null"; +#endif +} + +#ifdef __WIN32__ +bool event_poll(MSG &message) +{ + message.hwnd = 0; + message.message = 0; + message.wParam = 0; + message.lParam = 0; + if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE)) + return false; + TranslateMessage(&message); + DispatchMessage(&message); + return true; +} +#endif + +// makes a complaint about a failure. +#ifndef EMBEDDED_BUILD + #define COMPLAIN_QUERY(to_print) { \ + unlink(tmpfile.s()); \ + COMPLAIN(to_print); \ + } +#else + #define COMPLAIN_QUERY(to_print) { \ + COMPLAIN(to_print); \ + } +#endif + +#ifdef __UNIX__ +istring get_cmdline_from_proc() +{ + FUNCDEF("get_cmdline_from_proc"); + isprintf cmds_filename("/proc/%d/cmdline", portable::process_id()); + FILE *cmds_file = fopen(cmds_filename.s(), "r"); + if (!cmds_file) { + COMPLAIN("failed to open our process's command line file.\n"); + return "unknown"; + } + size_t size = 2000; + char *buff = new char[size + 1]; + ssize_t chars_read = getline(&buff, &size, cmds_file); + // read the first line, giving ample space for how long it might be. + fclose(cmds_file); // drop the file again. + if (!chars_read || negative(chars_read)) { + COMPLAIN("failed to get any characters from our process's cmdline file.\n"); + return "unknown"; + } + istring buffer = buff; + delete [] buff; + // clean out quote characters from the name. + for (int i = buffer.length() - 1; i >= 0; i--) { + if (buffer[i] == '"') buffer.zap(i, i); + } + return buffer; +} + +// deprecated; better to use the /proc/pid/cmdline file. +istring query_for_process_info() +{ + FUNCDEF("query_for_process_info"); + istring to_return = "unknown"; + // we ask the operating system about our process identifier and store + // the results in a temporary file. + chaos rando; + isprintf tmpfile("/tmp/proc_name_check_%d_%d.txt", portable::process_id(), + rando.inclusive(0, 128000)); + isprintf cmd("ps h --format \"%%a\" %d >%s", portable::process_id(), + tmpfile.s()); + // run the command to locate our process info. + int sysret = system(cmd.s()); + if (negative(sysret)) { + COMPLAIN_QUERY("failed to run ps command to get process info"); + return to_return; + } + // open the output file for reading. + FILE *output = fopen(tmpfile.s(), "r"); + if (!output) { + COMPLAIN_QUERY("failed to open the ps output file"); + return to_return; + } + // read the file's contents into a string buffer. + char buff[MAXIMUM_COMMAND_LINE]; + size_t size_read = fread(buff, 1, MAXIMUM_COMMAND_LINE, output); + if (size_read > 0) { + // success at finding some text in the file at least. + while (size_read > 0) { + const char to_check = buff[size_read - 1]; + if ( !to_check || (to_check == '\r') || (to_check == '\n') + || (to_check == '\t') ) + size_read--; + else break; + } + to_return.reset(istring::UNTERMINATED, buff, size_read); + } else { + // couldn't read anything. + COMPLAIN_QUERY("could not read output of process list"); + } + unlink(tmpfile.s()); + return to_return; +} +#endif + +// used as a return value when the name cannot be determined. +#ifndef EMBEDDED_BUILD + #define SET_BOGUS_NAME(error) { \ + COMPLAIN(error); \ + if (output) { \ + fclose(output); \ + unlink(tmpfile.s()); \ + } \ + istring home_dir = env_string("HOME"); \ + to_return = home_dir + "/failed_to_determine.exe"; \ + } +#else + #define SET_BOGUS_NAME(error) { \ + COMPLAIN(error); \ + to_return = "unknown"; \ + } +#endif + +istring application_name() +{ + FUNCDEF("application_name"); + istring to_return; +#ifdef __UNIX__ + to_return = get_cmdline_from_proc(); +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetModuleFileName(NIL, low_buff, MAX_ABS_PATH - 1); + istring buff = from_unicode_temp(low_buff); + buff.to_lower(); // we lower-case the name since windows seems to UC it. + to_return = buff; +#elif defined(EMBEDDED_BUILD) + SET_BOGUS_NAME("embedded_exe"); +#else + #pragma error("hmmm: no means of finding app name is implemented.") + SET_BOGUS_NAME("not_implemented_for_this_OS"); +#endif + return to_return; +} + +istring module_name(const void *module_handle) +{ +#ifdef __UNIX__ +//hmmm: implement module name for linux if that makes sense. + if (module_handle) {} + return application_name(); +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1); + istring buff = from_unicode_temp(low_buff); + buff.to_lower(); + return buff; +#else + #pragma message("module_name unknown for this operating system.") + return application_name(); +#endif +} + +u_int system_error() +{ +#if defined(__UNIX__) + return errno; +#elif defined(__WIN32__) + return GetLastError(); +#else + #pragma error("hmmm: no code for error number for this operating system") + return 0; +#endif +} + +istring system_error_text(u_int to_name) +{ +#if defined(__UNIX__) + return strerror(to_name); +#elif defined(__WIN32__) + char error_text[1000]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NIL, to_name, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)error_text, + sizeof(error_text) - 1, NIL); + istring to_return = error_text; + // trim off the ridiculous carriage return they add. + while ( (to_return[to_return.end()] == '\r') + || (to_return[to_return.end()] == '\n') ) + to_return.zap(to_return.end(), to_return.end()); + return to_return; +#else + #pragma error("hmmm: no code for error text for this operating system") + return ""; +#endif +} + +#ifdef __WIN32__ + +bool is_address_valid(const void *address, int size_expected, bool writable) +{ + return address && !IsBadReadPtr(address, size_expected) + && !(writable && IsBadWritePtr((void *)address, size_expected)); +} + +#endif // win32 + +version get_OS_version() +{ + version to_return; +#ifdef __UNIX__ + utsname kernel_parms; + uname(&kernel_parms); + to_return = version(kernel_parms.release); +#elif defined(__WIN32__) + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + ::GetVersionEx(&info); + to_return = version(isprintf("%u.%u.%u.%u", u_short(info.dwMajorVersion), + u_short(info.dwMinorVersion), u_short(info.dwPlatformId), + u_short(info.dwBuildNumber))); +#elif defined(EMBEDDED_BUILD) + // no version support. +#else + #pragma error("hmmm: need version info for this OS!") +#endif + return to_return; +} + +// for non-win32 and non-unix OSes, this might not work. +#if defined(__UNIX__) || defined(__WIN32__) + u_int process_id() { return getpid(); } +#else + #pragma error("hmmm: need process id implementation for this OS!") + u_int process_id() { return 0; } +#endif + +istring current_directory() +{ + istring to_return; +#ifdef __UNIX__ + char buff[MAX_ABS_PATH]; + getcwd(buff, MAX_ABS_PATH - 1); + to_return = buff; +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetCurrentDirectory(MAX_ABS_PATH, low_buff); + to_return = from_unicode_temp(low_buff); +#else + #pragma error("hmmm: need support for current directory on this OS.") + to_return = "."; +#endif + return to_return; +} + +istring env_string(const istring &variable_name) +{ +#ifdef __WIN32__ + char *value = getenv(variable_name.upper().observe()); + // dos & os/2 require upper case for the name, so we just do it that way. +#else + char *value = getenv(variable_name.observe()); + // reasonable OSes support mixed-case environment variables. +#endif + istring to_return; + if (value) + to_return = istring(value); + return to_return; +} + +bool set_environ(const istring &variable_name, const istring &value) +{ + int ret = 0; +#ifdef __WIN32__ + ret = putenv((variable_name + "=" + value).s()); +#else + ret = setenv(variable_name.s(), value.s(), true); +#endif + return !ret; +} + +timeval fill_timeval_ms(int duration) +{ + timeval time_out; // timeval has tv_sec=seconds, tv_usec=microseconds. + if (!duration) { + // duration is immediate for the check; just a quick poll. + time_out.tv_sec = 0; + time_out.tv_usec = 0; +#ifdef DEBUG_PORTABLE +// LOG("no duration specified"); +#endif + } else { + // a non-zero duration means we need to compute secs and usecs. + time_out.tv_sec = duration / 1000; + // set the number of seconds from the input in milliseconds. + duration -= time_out.tv_sec * 1000; + // now take out the chunk we've already recorded as seconds. + time_out.tv_usec = duration * 1000; + // set the number of microseconds from the remaining milliseconds. +#ifdef DEBUG_PORTABLE +// LOG(isprintf("duration of %d ms went to %d sec and %d usec.", duration, +// time_out.tv_sec, time_out.tv_usec)); +#endif + } + return time_out; +} + +//hmmm: this doesn't seem to account for quoting properly at all? +basis::char_star_array break_line(istring &app, const istring ¶meters) +{ + basis::char_star_array to_return; + int_array posns; + int num = 0; + // find the positions of the spaces and count them. + for (int j = 0; j < parameters.length(); j++) { + if (parameters[j] == ' ') { + num++; + posns += j; + } + } + // first, add the app name to the list of parms. + to_return += new char[app.length() + 1]; + app.stuff(to_return[0], app.length()); + int last_posn = 0; + // now add each space-separated parameter to the list. + for (int i = 0; i < num; i++) { + int len = posns[i] - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len); + last_posn = posns[i] + 1; + } + // catch anything left after last separator. + if (last_posn < parameters.length() - 1) { + int len = parameters.length() - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, parameters.length() - 1) + .stuff(to_return[to_return.last()], len); + } + // add the sentinel to the list of strings. + to_return += NIL; +#ifdef DEBUG_PORTABLE + for (int q = 0; to_return[q]; q++) { + printf("%d: %s\n", q, to_return[q]); + } +#endif + // now a special detour; fix the app name to remove quotes, which are + // not friendly to pass to exec. + if (app[0] == '"') app.zap(0, 0); + if (app[app.end()] == '"') app.zap(app.end(), app.end()); + return to_return; +} + +#ifdef __UNIX__ + +void exiting_child_signal_handler(int sig_num) +{ + FUNCDEF("exiting_child_signal_handler"); + if (sig_num != SIGCHLD) { + // uhhh, this seems wrong. + } + auto_synchronizer l(__process_synchronizer()); + for (int i = 0; i < __our_kids().length(); i++) { + int status; + pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG); + if ( (exited == -1) || (exited == __our_kids()[i]) ) { + // negative one denotes an error, which we are going to assume means the + // process has exited via some other method than our wait. if the value + // is the same as the process we waited for, that means it exited. + __our_kids().zap(i, i); + i--; + } else if (exited != 0) { + // zero would be okay; this result we do not understand. +#ifdef DEBUG_PORTABLE + printf("unknown result %d waiting for process %d", exited, + __our_kids()[i]); +#endif + } + } +} +#endif + +u_int launch_process(const istring &app_name_in, const istring &command_line, + int flag, u_int &child_id) +{ + child_id = 0; + istring app_name = app_name_in; + if (app_name[0] != '"') + app_name.insert(0, "\""); + if (app_name[app_name.end()] != '"') + app_name += "\""; +#ifdef __UNIX__ + // unix / linux implementation. + if (flag & RETURN_IMMEDIATELY) { + // they want to get back right away. + pid_t kid_pid = fork(); +#ifdef DEBUG_PORTABLE + printf("launch fork returned %d\n", kid_pid); +#endif + if (!kid_pid) { + // this is the child; we now need to launch into what we were asked for. +#ifdef DEBUG_PORTABLE + printf((isprintf("process %d execing ", process_id()) + app_name + + " parms " + command_line + "\n").s()); +#endif + basis::char_star_array parms = break_line(app_name, command_line); + execv(app_name.s(), parms.observe()); + // oops. failed to exec if we got to here. +#ifdef DEBUG_PORTABLE + printf((isprintf("child of fork (pid %d) failed to exec, " + "error is ", process_id()) + system_error_text(system_error()) + + "\n").s()); +#endif + exit(0); // leave since this is a failed child process. + } else { + // this is the parent. let's see if the launch worked. + if (kid_pid == -1) { + // failure. + u_int to_return = system_error(); +#ifdef DEBUG_PORTABLE + printf((isprintf("parent %d is returning after failing to create, " + "error is ", process_id()) + system_error_text(to_return) + + "\n").s()); +#endif + return to_return; + } else { + // yes, launch worked okay. + child_id = kid_pid; + { + auto_synchronizer l(__process_synchronizer()); + __our_kids() += kid_pid; + } + // hook in our child exit signal handler. + signal(SIGCHLD, exiting_child_signal_handler); + +#ifdef DEBUG_PORTABLE + printf((isprintf("parent %d is returning after successfully " + "creating %d ", process_id(), kid_pid) + app_name + + " parms " + command_line + "\n").s()); +#endif + return 0; + } + } + } else { + // assume they want to wait. + return system((app_name + " " + command_line).s()); + } +#elif defined(__WIN32__) + +//checking on whether we have admin rights for the launch. +//if (IsUserAnAdmin()) { +// MessageBox(0, (istring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} else { +// MessageBox(0, (istring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} + + PROCESS_INFORMATION process_info; + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + +#ifdef SUPPORT_SHELL_EXECUTE + if (flag & SHELL_EXECUTE) { + // new code for using shell execute method--required on vista for proper + // launching with the right security tokens. + int show_cmd = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. + show_cmd = SW_HIDE; + } else { + show_cmd = SW_SHOWNORMAL; + } + + SHELLEXECUTEINFO exec_info; + ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO)); + exec_info.cbSize = sizeof(SHELLEXECUTEINFO); + exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info. + | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs. + exec_info.hwnd = GetDesktopWindow(); +//hmmm: is get desktop window always appropriate? + to_unicode_persist(temp_verb, "open"); +//does "runas" work on xp also? or anywhere? + exec_info.lpVerb = temp_verb; + to_unicode_persist(temp_file, app_name); + exec_info.lpFile = temp_file; + to_unicode_persist(temp_parms, command_line); + exec_info.lpParameters = temp_parms; + exec_info.nShow = show_cmd; +// exec_info.hProcess = &process_info; + + BOOL worked = ShellExecuteEx(&exec_info); + if (!worked) + return system_error(); + // copy out the returned process handle. + process_info.hProcess = exec_info.hProcess; + process_info.dwProcessId = GetProcessId(exec_info.hProcess); + } else { +#endif //shell exec + // standard windows implementation using CreateProcess. + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + int create_flag = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. +// version ver = portable::get_OS_version(); +// version vista_version(6, 0); +// if (ver < vista_version) { +// // we suspect that this flag is hosing us in vista. + create_flag = CREATE_NO_WINDOW; +// } + } + istring parms = app_name + " " + command_line; + bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false, + create_flag, NIL, NIL, &startup_info, &process_info); + if (!success) + return system_error(); + // success then, merge back into stream. + +#ifdef SUPPORT_SHELL_EXECUTE + } +#endif //shell exec + + // common handling for CreateProcess and ShellExecuteEx. + child_id = process_info.dwProcessId; + u_long retval = 0; + if (flag & AWAIT_VIA_POLLING) { + // this type of waiting is done without blocking on the process. + while (true) { + MSG msg; + event_poll(msg); + // check if the process is gone yet. + BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval); + if (!ret) { + break; + } else { + // if they aren't saying it's still active, then we will leave. + if (retval != STILL_ACTIVE) + break; + } + sleep_ms(14); + } + } else if (flag & AWAIT_APP_EXIT) { + // they want to wait for the process to exit. + WaitForInputIdle(process_info.hProcess, INFINITE); + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &retval); + } + // drop the process and thread handles. + if (process_info.hProcess) + CloseHandle(process_info.hProcess); + if (process_info.hThread) + CloseHandle(process_info.hThread); + return (u_int)retval; +#else + #pragma error("hmmm: launch_process: no implementation for this OS.") +#endif + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +#ifdef __UNIX__ + +char *itoa(int to_convert, char *buffer, int radix) +{ + // slow implementation; may need enhancement for serious speed. + // ALSO: only supports base 10 and base 16 currently. + const char *formatter = "%d"; + if (radix == 16) formatter = "%x"; + isprintf printed(formatter, to_convert); + printed.stuff(buffer, printed.length() + 1); + return buffer; +} + +#endif +*/ + +#ifdef __WIN32__ + +/* +void show_wait_cursor() { SetCursor(LoadCursor(NULL, IDC_WAIT)); } + +void show_normal_cursor() { SetCursor(LoadCursor(NULL, IDC_ARROW)); } + +istring rc_string(UINT id, application_instance instance) +{ + flexichar temp[MAX_ABS_PATH + 1]; + int ret = LoadString(instance, id, temp, MAX_ABS_PATH); + if (!ret) return istring(); + return istring(from_unicode_temp(temp)); +} + +istring rc_string(u_int id) { return rc_string(id, GET_INSTANCE_HANDLE()); } +*/ + +const char *opsystem_name(known_operating_systems which) +{ + switch (which) { + case WIN_95: return "WIN_95"; + case WIN_NT: return "WIN_NT"; + case WIN_2K: return "WIN_2K"; + case WIN_XP: return "WIN_XP"; + case WIN_SRV2K3: return "WIN_SRV2K3"; + case WIN_VISTA: return "WIN_VISTA"; + case WIN_SRV2K8: return "WIN_SRV2K8"; + default: return "UNKNOWN_OS"; + } +} + +known_operating_systems determine_OS() +{ + version osver = application_configuration::get_OS_version(); + if ( (osver.v_major() == 4) && (osver.v_minor() == 0) ) { + if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) return WIN_95; + if (osver.v_revision() == VER_PLATFORM_WIN32_NT) return WIN_NT; + } else if ( (osver.v_major() == 5) && (osver.v_minor() == 0) ) { + return WIN_2K; + } else if ( (osver.v_major() == 5) && (osver.v_minor() == 1) ) { + return WIN_XP; + } else if ( (osver.v_major() == 5) && (osver.v_minor() == 2) ) { + return WIN_SRV2K3; + } else if ( (osver.v_major() == 6) && (osver.v_minor() == 0) ) { + return WIN_VISTA; + } else if ( (osver.v_major() == 6) && (osver.v_minor() == 1) ) { + return WIN_SRV2K8; + } + return UNKNOWN_OS; +} + +#endif // win32 + +} // namespace. + +#undef static_class_name + diff --git a/nucleus/library/application/windoze_helper.h b/nucleus/library/application/windoze_helper.h new file mode 100644 index 00000000..a02b2965 --- /dev/null +++ b/nucleus/library/application/windoze_helper.h @@ -0,0 +1,246 @@ +#ifndef WINDOZE_HELPER_GROUP +#define WINDOZE_HELPER_GROUP + +/* +* Name : windoze_helper definitions +* Author : Chris Koeritz +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! @file windoze_helper.h Aids in achievement of platform independence. +/*! @file windoze_helper.h + These definitions, inclusions and types are aimed at allowing our source + code to remain independent of the underlying windowing and operating + systems. Specifically, windows puts a lot of burden on the developer, + and this file exists to hide all that malarkey. +*/ + +// gnarly headers that are needed for certain types of compilation... + +//unix headers not needed in here for new purpose of file. +#ifdef __UNIX__ + #include +#endif +#ifndef NO_XWINDOWS + #ifdef __XWINDOWS__ + #include + #include + #include + #include + #endif +#endif +#ifdef __WIN32__ + #ifndef STRICT + #define STRICT + #endif + // windows headers... + #define _WINSOCKAPI_ // make windows.h happy about winsock. + #ifndef _AFXDLL + // include ms-windows headers only if we're not doing mfc; mfc has its own + // special way of including the headers. + #include + #else + #include + #include + #endif + // winsock support... + #undef FD_SETSIZE + #define FD_SETSIZE 1000 + // if you don't set this, you can only select on a default of 64 sockets. + #include +#endif + +// forward. +//class version; + +// wrapper classes defined in the sequel... +// +// application_instance -- this program's application object; used mainly +// in ms-windows. +// +// window_handle -- a wrapper for the root of all objects that can be +// referred to in the relevant windowing system. + +#include + +#ifdef __UNIX__ + // the application_instance class is implemented very simply for unix; + // it's a stand-in since unix apps don't refer to an instance handle like + // ms-windows does. + typedef void *application_instance; + + // some definitions to quiet complaints from win32-based code. + #ifndef LONGINT_SIZE + #if defined(__alpha) || (defined(__HOS_AIX__) && defined(_LP64)) \ + || defined(__sparcv9) || defined(__LP64__) + #define LONGINT_SIZE 8 + #else + #define LONGINT_SIZE 4 + #endif + #endif + #if (LONGINT_SIZE == 4) + // be compatible with linux's definitions on this subject. + typedef unsigned long DWORD; + #else + typedef unsigned int DWORD; + #endif + + typedef void *HANDLE; + + //temp; these just mimic function prototypes coming from non-portable code. + typedef void *ATOM; + typedef void *BITMAPINFO; + typedef void *HBITMAP; + typedef void *HDC; + typedef void *RGBQUAD; + #define LoadBitmap(a, b) (a) + #define __stdcall + #define afx_msg + + // windoze_helper definitions for the X windowing system. + #ifndef NO_XWINDOWS // protects regions with similar names. + #ifdef __XWINDOWS__ + typedef Widget window_handle; + //!< main way to access a windowing system object. should be a pointer. + + typedef Colormap window_colormap; + //!< the windowing system representation of a pallette or colormap. + + typedef XColor window_color; + //!< the system representation of color. + + typedef XmString window_string; + //!< a special windowing system dependent kind of character string. + +//is that really fixed? + const int MAXIMUM_COLOR_INTENSITY = 65535; + // largest valid value a color component (R, G or B) can have. + #else + // no x-windows. + typedef void *window_handle; + #endif + #endif +#endif + +#ifdef __WIN32__ + typedef HINSTANCE application_instance; + // our moniker for an application's guts. + + typedef HWND window_handle; + // our alias for window handles in win32. +#endif + +namespace application { + +// istring module_name(const void *module_handle = NIL); + //!< returns the name of the module for the "module_handle" where supported. + /*!< if the handle is NIL, then the program name is returned. */ + + +// u_int system_error(); + //!< gets the most recent system error reported on this thread. + +// istring system_error_text(u_int error_to_show); + //!< returns the OS's string form of the "error_to_show". + /*!< this often comes from the value reported by system_error(). */ + +// istring null_device(); + //!< returns the name of the system's NULL device. + /*!< this device is a black hole where output can be sent, never to be + seen again. this is /dev/null on unix and null: on win32. */ + +// timeval fill_timeval_ms(int milliseconds); + //!< returns a timeval system object that represents the "milliseconds". + /*!< if "milliseconds" is zero, then the returned timeval will + represent zero time passing (rather than infinite duration as some + functions assume). */ + + // this only really helps for win32. + extern application_instance _i_handle; + //!< dll_root.cpp defines this for all dlls. + #define DEFINE_INSTANCE_HANDLE application_instance application::_i_handle = 0 + //!< some applications may need this to use rc_string and others. + /*!< if this macro is used, then the code is impervious to future + changes in how the instance handle works. */ + #define SET_INSTANCE_HANDLE(value) application::_i_handle = value + //!< use this to change the instance handle for this dll or exe. + /*!< note that this should be done only once and by the main thread. */ + #define GET_INSTANCE_HANDLE() application::_i_handle + //!< a handy macro that frees one from knowing the name of the handle. + +//////////////////////////////////////////////////////////////////////////// + + // Unix and Linux specific items are included here. + #ifdef __UNIX__ +//// istring get_cmdline_from_proc(); + //!< returns the command line that /proc has recorded for our process. + +// char *itoa(int to_convert, char *buffer, int radix); + //!< turns the integer "to_convert" into a string stored in the "buffer". + /*!< this is needed on linux since there doesn't seem to be a builtin + version of it. the buffer must be long enough to hold the number's + textual representation! the buffer's length must also account for the + chosen "radix" (the base for the number system, where 10 is decimal, + 16 is hexadecimal, etc). */ + + #define RGB(r, g, b) (b + (g << 8) + (r << 16)) + //!< no idea if that's even approximately right. + #endif + + // ms-windows of more modern types, i.e. win32. + #ifdef __WIN32__ + +// bool event_poll(MSG &message); + //!< tries to process one win32 event and retrieve the "message" from it. + /*!< this is a very general poll and will retrieve any message that's + available for the current thread. the message is actually processed + here also, by calling translate and dispatch. the returned structure + is mainly interesting for knowing what was done. */ + +//hmmm: is there an equivalent to this for unix? +// bool is_address_valid(const void *address, int size_expected, +// bool writable = false); + //!< checks that the address specified is a valid pointer. + /*! also tests that the address's allocated space has the + "size_expected". if "writable" is true, then the pointer is + checked for being writable as well as readable. */ + + #define BROADCAST_HANDLE HWND_BROADCAST + + enum known_operating_systems { + WIN_95, WIN_NT, WIN_2K, WIN_XP, WIN_SRV2K3, WIN_VISTA, WIN_SRV2K8, + UNKNOWN_OS + }; + const char *opsystem_name(known_operating_systems which); + //!< returns the textual form of the known_operating_systems enum. + + known_operating_systems determine_OS(); + //!< returns the operating system that seems to be running currently. + /*!< note that WIN_95 also covers windows 98 and windows ME. */ + +// istring rc_string(u_int id, application_instance instance); + //!< acquires a string from a particular "instance". + /*!< the "id" is the resource identifier for the desired string in + that "instance". */ + +// istring rc_string(u_int id); + //!< simplified from above function by not needing an "instance". + /*!< the "current" dynamic library or executable's resources are sought + for the "id". */ + +// void show_wait_cursor(); + //!< changes the cursor to the "wait" look, which is usually an hourglass. +// void show_normal_cursor(); + //!< changes the cursor to "normal", which is usually a pointing arrow. + + #endif // win32. + +} // namespace. + +#endif // outer guard. + diff --git a/nucleus/library/basis/array.h b/nucleus/library/basis/array.h new file mode 100644 index 00000000..afd703e6 --- /dev/null +++ b/nucleus/library/basis/array.h @@ -0,0 +1,871 @@ +#ifndef ARRAY_CLASS +#define ARRAY_CLASS + +/*****************************************************************************\ +* * +* Name : array * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "common_outcomes.h" +#include "definitions.h" +#include "enhance_cpp.h" +#include "functions.h" +#include "guards.h" +#include "outcome.h" + +#define DEBUG_ARRAY + // uncomment for the noisier debugging version. + +namespace basis { + +//! Represents a sequential, ordered, contiguous collection of objects. +/*! + This object manages a contiguous array of memory to hold the objects it + contains. The objects to be stored must have a constructor with zero + parameters, since the objects are stored in a C-style array (and array + constructors cannot be given arguments to be passed to the objects). + The objects must also either be flat (containing no pointers) or have an + assignment operator (=) that correctly copies the deep contents. + This class also provides an exponential growth mode for memory to reduce + thrashing; this allows the size pre-allocated to double every time a new + allocation is required during a resize. This causes the allocation to grow + very swiftly, speeding up usage of frequently growing arrays, but this may + not be desired for every array. + terms used here... + blank array: a array with some number of elements, but where those + elements are objects that have been constructed using their default + parameterless constructor. + empty array: a array of zero elements. +*/ + +template +class array : public virtual root_object +{ +public: + //! the flags specify how the array treats its contents and its length. + enum specialc_flags { + NO_SPECIAL_MODES = 0x0, //!< do nothing extra; only valid by itself. + SIMPLE_COPY = 0x1, //!< the contents can be memcpy'd and are not deep. + EXPONENTIAL_GROWTH = 0x2, //!< length is doubled when reallocation happens. + EXPONE = EXPONENTIAL_GROWTH, //!< synonym for EXPONENTIAL_GROWTH. + FLUSH_INVISIBLE = 0x4 //!< blanks out allocated but inaccessible elements. + }; + + DEFINE_CLASS_NAME("array"); + + enum how_to_copy { NEW_AT_END, NEW_AT_BEGINNING, DONT_COPY }; + //!< An enumeration of ways to shift existing contents in an array. + /*!< In the context of dynamically resizing an array of data, a range of + new elements can be added (or removed for that matter) at the end of the + existing space (NEW_AT_END), at the beginning of the existing space + (NEW_AT_BEGINNING), or we might not care about the existing data when + we perform the resize (DONT_COPY). These methods impose a particular + shift on the existing contents if we actually already have enough space + for the new contents. If there is space before the part of the array + that's in use and we want NEW_AT_END, then the existing contents are + jammed up into the front end of the array. */ + + array(int number = 0, const contents *init = NIL, + int flags = EXPONENTIAL_GROWTH | FLUSH_INVISIBLE); + //!< Constructs an array with room for "number" objects. + /*!< The initial contents are copied from "init" unless NIL is passed in + instead. If "init" is not NIL, then it must point to an array of objects + that contains at least "number". The "flags" are a value based on the + special flags being added bit-wise. If "flags" contains SIMPLE_COPY, then + memmove() is used rather than using the C++ object's assignment operator. + Note that SIMPLE_COPY will NOT work if the templated object has a regular + constructor or assignment operator, since those methods will not be + called on copying. If the "flags" contain EXPONENTIAL_GROWTH, then the + true allocation size will be doubled every time a new allocation is + required. when the FLUSH_INVISIBLE flag is included, then the array + elements that go out of scope are returned to the state provided by + the content's default constructor. this ensures that if they ever come + back into scope, they do not yet have any contents. further, if the + elements had any deep contents, those resources should be released. */ + + array(const array ©_from); + //!< copies the contents & sizing information from "copy_from". + + virtual ~array(); //!< destroys the memory allocated for the objects. + + void reset(int number = 0, const contents *initial_contents = NIL); + //!< Resizes this array and sets the contents from an array of contents. + /*< If "initial_contents" is not NIL, then it must contain an array of + "contents" with at least "number" objects in it. If it is NIL, then + the size of the array is changed but the contents are not. note that + any pre-existing elements that were previously out of range might still + have their prior contents; the newly available elements are not necessarily + "blank". thus, before using them, ensure you store appropriate elements + in those positions. */ + + array &operator = (const array ©_from); + //!< Copies the array in "copy_from" into this. + + int length() const { return c_active_length; } + //!< Returns the current reported length of the allocated C array. + + int last() const { return c_active_length - 1; } + //!< Returns the last valid element in the array. + + int flags() const { return c_flags; } + //!< Provides the raw flags value, without interpreting what it means. + + bool exponential() const { return c_flags & EXPONENTIAL_GROWTH; } + //!< Returns true if this allocator will grow exponentially on resize. + + bool simple() const { return c_flags & SIMPLE_COPY; } + //!< Reports whether the templated object is a simple type or not. + + const contents &get(int index) const; + //!< Accesses individual objects stored in "this" at the "index" position. + /*!< If the index is out of range, then a bogus reference (to internally + held garbage) is returned. */ + contents &use(int index); + //!< A non-constant version of get(); the returned object can be modified. + + const contents &operator [] (int index) const { return get(index); } + //!< Synonym for get that provides the expected array indexing syntax. + contents &operator [] (int index) { return use(index); } + //!< Synonym for use that provides the expected array indexing syntax. + + outcome put(int index, const contents &to_put); + //!< Stores an object at the index "index" in the array. + /*!< The outcome is "OUT_OF_RANGE" if the index does not exist. */ + + array concatenation(const array &to_concatenate) const; + //!< Returns the concatenation of "this" and the array "to_concatenate". + array concatenation(const contents &to_concatenate) const; + //!< Returns the concatenation of "this" and the object "to_concatenate". + + array &concatenate(const array &to_concatenate); + //!< Appends the array "to_concatenate" onto "this" and returns "this". + array &concatenate(const contents &to_concatenate); + //!< Appends the object "to_concatenate" onto "this" and returns "this". + array &concatenate(const contents *to_concatenate, int length); + //!< Concatenates a C-array "to_concatenate" onto "this" and returns "this". + /*!< There must be at least "length" elements in "to_concatenate". */ + + array operator + (const array &to_cat) const + { return concatenation(to_cat); } + //!< Synonym for concatenation. + array operator + (const contents &to_concatenate) const + { return concatenation(to_concatenate); } + //!< Synonym for concatenation. + array &operator += (const array &to_concatenate) + { return concatenate(to_concatenate); } + //!< Synonym for concatenate that modifies "this". + array &operator += (const contents &to_concatenate) + { return concatenate(to_concatenate); } + //!< Synonym for concatenate that modifies "this". + + const contents *observe() const { return c_offset; } + //!< Returns a pointer to the underlying C array of data. + /*!< The array contains "length()" number of objects in it. BE CAREFUL. + This version is a constant peek at that pointer. */ + contents *access() { return c_offset; } + //!< A non-constant access of the underlying C-array. BE REALLY CAREFUL. + + void swap_contents(array &other); + //!< Exchanges the contents of "this" and "other". + /*!< No validation is performed but this should always succeed given + arrays constructed properly. */ + + void snarf(array &new_contents); + //!< Drops "this" array's contents into the dustbin and uses "new_contents". + /*!< Afterwards, "new_contents" is an empty array and what used to be + stored there is now in "this" instead. */ + + array subarray(int start, int end) const; + //!< Returns the array segment between the indices "start" and "end". + /*!< This is all characters from "start" to "end" inclusively, as long as + those values are valid for this array. Even then, an intelligent default + is usually assumed if the indices are out of range. */ + + outcome insert(int index, int new_indices); + //!< Adds "new_indices" new positions for objects into the array at "index". + + outcome overwrite(int index, const array &write_with, int count = -1); + //!< Stores the array "write_with" into the current array at the "index". + /*!< The current contents are overwritten with "write_with". If the + index is invalid, then OUT_OF_RANGE is returned. If the "write_with" + array cannot fit due to the boundaries of "this" array, then only the + portion that can fit is used. If "count" is negative, the whole + "write_with" array is used; otherwise, only "count" elements are used. */ + + outcome stuff(int length, contents *to_stuff) const; + //!< Copies at most "length" elements from this into the array "to_stuff". + /*!< This call will fail disastrously if "length" is larger than the + array "to_stuff"'s allocated length. */ + + outcome resize(int new_size, how_to_copy way = NEW_AT_END); + //!< Changes the size of the C array to "new_size". + /*!< If "way" is NEW_AT_END and the array grows, then new space is added + at the end and the prefix of the array is the same as the old array. If + the "way" is NEW_AT_END, but the array shrinks, then the new array is a + prefix of the old array. If "way" is NEW_AT_BEGINNING and the array + grows, then the suffix of the array is the same as the old one and the + space is added at the beginning. if the "way" is NEW_AT_BEGINNING but the + array shrinks, then the new array is a suffix of the old array. if "way" + is DONT_COPY, then the old contents are not copied. keep in mind that + any newly visible elements can be in an arbitrary state and will not + necessarily be freshly constructed. */ + + outcome zap(int start, int end); + //!< Deletes from "this" the objects inclusively between "start" and "end". + /*!< C-array conventions are used (0 through length()-1 are valid if + length() > 0). If either index is out of range, then a default is + assumed. */ + + outcome shrink(); + //!< Cuts loose any allocated space that is beyond the real length. + + outcome retrain(int new_size, const contents *to_copy); + //!< Resizes the C array and stuffs it with the contents in "to_copy". + + enum shift_directions { TO_LEFT, TO_RIGHT }; + //!< All the real contents can be shifted to either the left or right. + + void shift_data(shift_directions where); + //!< The valid portion of the array is moved to the left or right. + /*!< This means that the unused space (dictated by the offset where the + data starts) will be adjusted. This may involve copying the data. */ + + // These are gritty internal information methods and should not be used + // except by appropriately careful code. + int internal_real_length() const { return c_real_length; } + //!< Gritty Internal: the real allocated length. + int internal_offset() const { return int(c_offset - c_mem_block); } + //!< Gritty Internal: the offset from real start to stored data. + const contents *internal_block_start() const { return c_mem_block; } + //!< Gritty Internal: constant peek at the real allocated pointer. + contents *internal_block_start() { return c_mem_block; } + //!< Gritty Internal: the real allocated pointer made accessible. + contents * const *internal_offset_mem() const { return &c_offset; } + //!< Gritty Internal: the start of the actual stored data. + +private: + int c_active_length; //!< the number of objects reported to be in the array. + int c_real_length; // the real number of objects that can be stored. + contents *c_mem_block; //!< a pointer to the objects held for this array. + contents *c_offset; //!< the beginning of the useful part of the memory block. + int c_flags; //!< records the special characteristics of this array. + + outcome allocator_reset(int initial_elements, int blocking); + //!< Allocates space for the "initial_elements" plus the "blocking" factor. + /*!< This resets the C-array to have "initial_elements" indices, plus an + extra pre-allocation of "blocking" that leaves room for expansion. Any + prior contents are whacked. */ +}; + +////////////// + +//! A simple object that wraps a templated array of ints. +class int_array : public array +{ +public: + int_array(int number = 0, const int *initial_contents = 0) + : root_object(), + array(number, initial_contents, SIMPLE_COPY | EXPONE) {} + //!< Constructs an array of "number" integers. + /*!< creates a list of ints based on an initial "number" of entries and + some "initial_contents", which should be a regular C array of ints + with at least as many entries as "number". */ +}; + +////////////// + +//! An array of double floating point numbers. +class double_array : public array +{ +public: + double_array(int len = 0, double *data = NIL) + : root_object(), + array(len, data, SIMPLE_COPY | EXPONE) {} + double_array(const array &to_copy) : array(to_copy) {} +}; + +////////////// + +// implementation code, much longer methods below... + +// GOALS: +// +// 1) provide a slightly smarter allocation method for C arrays and other +// contiguous-storage container classes with better speed and reduced memory +// fragmentation through pre-allocation. this can reduce memory thrashing +// when doing appends and inserts that can be granted with previously +// allocated, but unused, space. +// 2) clean-up bounds failure cases in functions that return a reference by +// always having at least one bogus element in the array for returns. this +// really just requires that we never allow our hidden real length of the +// array to be zero. + +template +array::array(int num, const contents *init, int flags) +: root_object(), c_active_length(0), c_real_length(0), c_mem_block(NIL), c_offset(NIL), c_flags(flags) +{ + if (c_flags > 7) { +#ifdef DEBUG_ARRAY + throw "error: array::constructor: error in parameters! still passing a block size?"; +#endif + c_flags = EXPONE | FLUSH_INVISIBLE; + // drop simple copy, since the caller doesn't know what they're doing. + } + + allocator_reset(num, 1); // get some space. + retrain(num, init); // plug in their contents. +} + +template +array::array(const array &cf) +: root_object(), c_active_length(0), c_real_length(0), c_mem_block(NIL), c_offset(NIL), c_flags(cf.c_flags) +{ + allocator_reset(cf.c_active_length, 1); // get some space. + operator = (cf); // assignment operator does the rest. +} + +template +array::~array() +{ + c_offset = NIL; + if (c_mem_block) delete [] c_mem_block; + c_mem_block = NIL; + c_active_length = 0; + c_real_length = 0; +} + +template +void array::reset(int num, const contents *init) +{ retrain(num, init); } + +template +array &array::operator =(const array &cf) +{ + if (this == &cf) return *this; + c_flags = cf.c_flags; // copy the flags coming in from the other object. + // prepare the array for retraining... + c_offset = c_mem_block; // slide the offset back to the start. + c_active_length = 0; // the length gets reset also. + retrain(cf.c_active_length, cf.observe()); + return *this; +} + +template +contents &array::use(int index) +{ + bounds_return(index, 0, this->last(), bogonic()); + return this->access()[index]; +} + +template +const contents &array::get(int index) const +{ + bounds_return(index, 0, this->last(), bogonic()); + return this->observe()[index]; +} + +template +array &array::concatenate(const array &s1) +{ + // check whether there's anything to concatenate. + if (!s1.length()) return *this; + if (this == &s1) { + // make sure they don't concatenate this array to itself. + return concatenate(array(*this)); + } + int old_len = this->length(); + resize(this->length() + s1.length(), NEW_AT_END); + overwrite(old_len, s1); + return *this; +} + +template +array &array::concatenate(const contents &to_concatenate) +{ + resize(this->length() + 1, NEW_AT_END); + if (!this->simple()) + this->access()[this->last()] = to_concatenate; + else + memcpy(&(this->access()[this->last()]), &to_concatenate, sizeof(contents)); + return *this; +} + +template +array &array::concatenate(const contents *to_concatenate, + int length) +{ + if (!length) return *this; // nothing to do. + const int old_len = this->length(); + resize(this->length() + length, NEW_AT_END); + if (!this->simple()) + for (int i = 0; i < length; i++) + this->access()[old_len + i] = to_concatenate[i]; + else + memcpy(&(this->access()[old_len]), to_concatenate, + length * sizeof(contents)); + return *this; +} + +template +array array::concatenation(const array &s1) const +{ + // tailor the return array to the new size needed. + array to_return(this->length() + s1.length(), NIL, s1.c_flags); + to_return.overwrite(0, *this); // put the first part into the new array. + to_return.overwrite(this->length(), s1); // add the second segment. + return to_return; +} + +template +array array::concatenation(const contents &s1) const +{ + array to_return(this->length() + 1, NIL, c_flags); + to_return.overwrite(0, *this); + if (!this->simple()) + to_return.access()[to_return.last()] = s1; + else + memcpy(&(to_return.access()[to_return.last()]), &s1, sizeof(contents)); + return to_return; +} + +template +array array::subarray(int start, int end) const +{ + bounds_return(start, 0, this->last(), array(0, NIL, c_flags)); + bounds_return(end, 0, this->last(), array(0, NIL, c_flags)); + if (start > end) return array(0, NIL, c_flags); + return array(end - start + 1, &(this->observe()[start]), c_flags); +} + +template +void array::swap_contents(array &other) +{ + if (this == &other) return; // already swapped then, i suppose. + swap_values(this->c_active_length, other.c_active_length); + swap_values(this->c_real_length, other.c_real_length); + swap_values(this->c_offset, other.c_offset); + swap_values(this->c_mem_block, other.c_mem_block); + swap_values(this->c_flags, other.c_flags); +} + +template +outcome array::shrink() +{ + if (!c_mem_block) return common::OUT_OF_MEMORY; + if (c_active_length == c_real_length) return common::OKAY; // already just right. + array new_holder(*this); + // create a copy of this object that is just the size needed. + swap_contents(new_holder); + // swap this object with the copy, leaving the enlarged version behind + // for destruction. + return common::OKAY; +} + +template +outcome array::stuff(int lengthx, contents *to_stuff) const +{ + if (!lengthx || !this->length()) return common::OKAY; + int copy_len = minimum(lengthx, this->length()); + if (!this->simple()) { + for (int i = 0; i < copy_len; i++) + to_stuff[i] = this->observe()[i]; + } else { + memcpy(to_stuff, this->observe(), copy_len * sizeof(contents)); + } + return common::OKAY; +} + +template +outcome array::overwrite(int position, + const array &write_with, int count) +{ + if (!count) return common::OKAY; + if ( (this == &write_with) || !this->length() || !write_with.length()) + return common::BAD_INPUT; + bounds_return(position, 0, this->last(), common::OUT_OF_RANGE); + if ( negative(count) || (count > write_with.length()) ) + count = write_with.length(); + if (position > this->length() - count) + count = this->length() - position; + if (!this->simple()) { + for (int i = position; i < position + count; i++) + this->access()[i] = write_with.observe()[i - position]; + } else { + memcpy(&(this->access()[position]), write_with.observe(), + count * sizeof(contents)); + } + return common::OKAY; +} + +template +outcome array::allocator_reset(int initial, int blocking) +{ +// FUNCDEF("allocator_reset") + if (blocking < 1) { +#ifdef DEBUG_ARRAY + throw "error: array::allocator_reset: has bad block size"; +#endif + blocking = 1; + } + if (initial < 0) initial = 0; // no antimatter arrays. + if (c_mem_block) { + // remove old contents. + delete [] c_mem_block; + c_mem_block = NIL; + c_offset = NIL; + } + c_active_length = initial; // reset the length to the reporting size. + c_real_length = initial + blocking; // compute the real length. + if (c_real_length) { + c_mem_block = new contents[c_real_length]; + if (!c_mem_block) { + // this is an odd situation; memory allocation didn't blow out an + // exception, but the memory block is empty. let's consider that + // a fatal error; we can't issue empty objects. + throw common::OUT_OF_MEMORY; + } + c_offset = c_mem_block; // reset offset to start of array. + } + return common::OKAY; +} + +template +void array::shift_data(shift_directions where) +{ + if (where == TO_LEFT) { + // we want to end up with the data jammed up against the left edge. thus + // we need the offset to be zero bytes from start. + if (c_offset == c_mem_block) + return; // offset already at start, we're done. + // well, we need to move the data. + if (simple()) { + memmove(c_mem_block, c_offset, c_active_length * sizeof(contents)); + } else { + for (contents *ptr = c_offset; ptr < c_offset + c_active_length; ptr++) + c_mem_block[ptr - c_offset] = *ptr; + } + c_offset = c_mem_block; // we've ensured that this is correct. + if (c_flags & FLUSH_INVISIBLE) { + // we need to clean up what might have had contents previously. +// for (contents *p = c_mem_block + c_active_length; p < c_mem_block + c_real_length; p++) +// *p = contents(); + } + } else { + // we want to move the data to the right, so the offset should be the + // difference between the real length and the length. + if (c_offset == c_mem_block + c_real_length - c_active_length) + return; // the offset is already the right size. + if (simple()) { + memmove(&c_mem_block[c_real_length - c_active_length], c_offset, c_active_length * sizeof(contents)); + } else { + for (int i = c_real_length - 1; i >= c_real_length - c_active_length; i--) + c_mem_block[i] = c_offset[i - c_real_length + c_active_length]; + } + c_offset = c_mem_block + c_real_length - c_active_length; // we've now ensured this. + if (c_flags & FLUSH_INVISIBLE) { + // we need to clean up the space on the left where old contents might be. +// for (contents *p = c_mem_block; p < c_offset; p++) +// *p = contents(); + } + } +} + +template +outcome array::resize(int new_size, how_to_copy way) +{ +/// FUNCDEF("resize"); + if (new_size < 0) new_size = 0; // we stifle this. + if (new_size == c_active_length) { + // nothing much to do. + return common::OKAY; + } + // okay, now we at least know that the sizes are different. we save all the + // old information about the array prior to this resizing. + contents *old_s = c_mem_block; // save the old contents... + const int old_len = c_active_length; // and length. + contents *old_off = c_offset; // and offset. + bool delete_old = false; // if true, old memory is whacked once it's copied. + +//hmmm: wasn't there a nice realization that we could bail out early in +// the case where the size is already suffcient? there seems to be +// an extraneous copy case here. +// also it would be nice to have better, more descriptive names for the +// variables here since they are not lending themselves to understanding +// the algorithm. + + // we check whether there's easily enough space in the array already. + // if not, then we have some more decisions to make. + if (c_real_length - (old_off - old_s) < new_size) { + // well, there's not enough space with the current space and offset. + if (c_real_length < new_size) { + // there's really not enough space overall, no fooling. we now will + // create a new block. + c_mem_block = NIL; // zero out the pointer so reset doesn't delete it. + delete_old = true; + int blocking = 1; + if (exponential()) blocking = new_size + 1; + outcome ret = allocator_reset(new_size, blocking); + if (ret != common::OKAY) { + // failure, but don't forget to whack the old glob. +#ifdef DEBUG_ARRAY + throw "error: array::resize: saw array reset failure"; +#endif + delete [] old_s; + return ret; + } + // fall out to the copying phase, now that we have some fresh memory. + } else { + // there is enough space if we shift some things around. + const int size_difference = new_size - c_active_length; + // we compute how much space has to be found in the array somewhere + // to support the new larger size. + if (way == DONT_COPY) { + // simplest case; just reset the offset appropriately so the new_size + // will fit. + c_offset = c_mem_block; + c_active_length = new_size; + } else if (way == NEW_AT_BEGINNING) { + // if the new space is at the beginning, there are two cases. either + // the new size can be accomodated by the current position of the + // data or the data must be shifted to the right. + if (c_offset - c_mem_block < size_difference) { + // we need to shift the data over to the right since the offset isn't + // big enough for the size increase. + shift_data(TO_RIGHT); // resets the offset appropriately. + } + // now we know that the amount of space prior to the real data + // is sufficient to hold what new space is needed. we just need to + // shift the offset back somewhat. + c_offset -= size_difference; + c_active_length = new_size; + } else { + // better only be three ways to do this; we're now assuming the new + // space should be at the end (NEW_AT_END). + // now that we're here, we know there will be enough space if we shift + // the block to the left, but we DO NEED to do this. if we didn't need + // to shift the data, then we would find that: + // c_real_length - old_off >= new_size + // which is disallowed by the guardian conditional around this area. + shift_data(TO_LEFT); // resets the offset for us. + c_active_length = new_size; + } + // we have ensured that we had enough space and we have already shifted + // the data around appropriately. we no longer need to enter the next + // block where we would copy data around if we had to. it has become + // primarily for cases where either we have to copy data because we + // have new storage to fill or where we are shrinking the array. + return common::OKAY; + } + } + + // the blob of code below is offset invariant. by the time we reach here, + // the array should be big enough and the offset should be okay. + c_active_length = new_size; // set length to the new reporting size. + if (way != DONT_COPY) { + int where = 0; // offset for storing into new array. + bool do_copy = false; // if true, then we need to move the data around. + contents *loopc_offset_old = old_off; // offset into original object. + // we should only have to copy the memory in one other case besides our + // inhabiting new memory--when we are asked to resize with the new stuff + // at the beginning of the array. if the new space is at the end, we're + // already looking proper, but if the new stuff is at the beginning, we + // need to shift existing stuff downwards. + if (way == NEW_AT_BEGINNING) { + where = new_size - old_len; // move up existing junk. + if (where) do_copy = true; // do copy, since it's not immobile. + if (where < 0) { + // array shrank; we need to do the loop differently for starting + // from the beginning. we skip to the point in the array that our + // suffix needs to start at. + loopc_offset_old -= where; + where = 0; // reset where so we don't have negative offsets. + } + } + const int size_now = minimum(old_len, c_active_length); + if (delete_old || do_copy) { + contents *offset_in_new = c_offset + where; + contents *posn_in_old = loopc_offset_old; + if (simple()) { + // memmove should take care of intersections. + memmove(offset_in_new, posn_in_old, size_now * sizeof(contents)); + } else { + // we need to do the copies using the object's assignment operator. + if (new_size >= old_len) { + for (int i = size_now - 1; i >= 0; i--) + offset_in_new[i] = posn_in_old[i]; + } else { + for (int i = 0; i < size_now; i++) + offset_in_new[i] = posn_in_old[i]; + } + } + + // we only want to flush the obscured elements when we aren't already + // inhabiting new space. + if ( (c_flags & FLUSH_INVISIBLE) && !delete_old) { + // clear out the space that just went out of scope. we only do this + // for the flushing mode and when we aren't starting from a fresh + // pointer (i.e., delete_old isn't true). + if (new_size < old_len) { +// for (contents *p = posn_in_old; p < offset_in_new; p++) +// *p = contents(); + } + } + } + } + if (delete_old) delete [] old_s; + return common::OKAY; +} + +template +outcome array::retrain(int new_len, const contents *to_set) +{ +/// FUNCDEF("retrain") + if (new_len < 0) new_len = 0; // stifle that bad length. +#ifdef DEBUG_ARRAY + if (to_set && (c_mem_block >= to_set) && (c_mem_block < to_set + new_len) ) { + throw "error: array::retrain: ranges overlap in retrain!"; + } +#endif + outcome ret = resize(new_len, DONT_COPY); + if (ret != common::OKAY) return ret; +#ifdef DEBUG_ARRAY + if (new_len != c_active_length) { + throw "error: array resize set the wrong length"; + } +#endif + if (to_set) { + if (simple()) + memcpy(c_offset, to_set, c_active_length * sizeof(contents)); + else + for (int i = 0; i < c_active_length; i++) + c_offset[i] = to_set[i]; + } else { + if (c_flags & FLUSH_INVISIBLE) { + // no contents provided, so stuff the space with blanks. +// for (int i = 0; i < c_active_length; i++) c_offset[i] = contents(); + } + } + if (c_flags & FLUSH_INVISIBLE) { +// for (contents *ptr = c_mem_block; ptr < c_offset; ptr++) +// *ptr = contents(); +// for (contents *ptr = c_offset + c_active_length; ptr < c_mem_block + c_real_length; ptr++) +// *ptr = contents(); + } + return common::OKAY; +} + +template +outcome array::zap(int position1, int position2) +{ + if (position1 > position2) return common::OKAY; + bounds_return(position1, 0, c_active_length - 1, common::OUT_OF_RANGE); + bounds_return(position2, 0, c_active_length - 1, common::OUT_OF_RANGE); + if (!position1) { + // if they're whacking from the beginning, we just reset the offset. + c_offset += position2 + 1; + c_active_length -= position2 + 1; + return common::OKAY; + } + const int difference = position2 - position1 + 1; + // copy from just above position2 down into position1. + if (simple()) { + if (c_active_length - difference - position1 > 0) + memmove(&c_offset[position1], &c_offset[position1 + difference], + (c_active_length - difference - position1) * sizeof(contents)); + } else { + for (int i = position1; i < c_active_length - difference; i++) + c_offset[i] = c_offset[i + difference]; + } + + outcome ret = resize(c_active_length - difference, NEW_AT_END); + // chop down to new size. +#ifdef DEBUG_ARRAY + if (ret != common::OKAY) { + throw "error: array::zap: resize failure"; + return ret; + } +#endif + return ret; +} + +template +outcome array::insert(int position, int elem_to_add) +{ + if (position < 0) return common::OUT_OF_RANGE; + if (position > this->length()) + position = this->length(); + if (elem_to_add < 0) return common::OUT_OF_RANGE; + how_to_copy how = NEW_AT_END; + if (position == 0) how = NEW_AT_BEGINNING; + resize(this->length() + elem_to_add, how); + + // if the insert wasn't at the front, we have to copy stuff into the new + // locations. + if (how == NEW_AT_END) { + const contents simple_default_object = contents(); + if (!this->simple()) { + for (int i = this->last(); i >= position + elem_to_add; i--) + this->access()[i] = this->observe()[i - elem_to_add]; + for (int j = position; j < position + elem_to_add; j++) + this->access()[j] = simple_default_object; + } else { + memmove(&(this->access()[position + elem_to_add]), + &(this->observe()[position]), (this->length() - position + - elem_to_add) * sizeof(contents)); + for (int j = position; j < position + elem_to_add; j++) + memcpy(&this->access()[j], &simple_default_object, sizeof(contents)); + } + } + return common::OKAY; +} + +template +outcome array::put(int index, const contents &to_put) +{ + bounds_return(index, 0, this->last(), common::OUT_OF_RANGE); + if (!this->simple()) + this->access()[index] = to_put; + else + memcpy(&(this->access()[index]), &to_put, sizeof(contents)); + return common::OKAY; +} + +template +void array::snarf(array &new_contents) +{ + if (this == &new_contents) return; // no blasting own feet off. + reset(); // trash our current storage. + swap_contents(new_contents); +} + +/* +//! a simple wrapper of an array of char *, used by portable::break_line(). +class char_star_array : public array +{ +public: + char_star_array() : array(0, NIL, SIMPLE_COPY | EXPONE + | FLUSH_INVISIBLE) {} + ~char_star_array() { + // clean up all the memory we're holding. + for (int i = 0; i < length(); i++) { + delete [] (use(i)); + } + } +}; +*/ + +} //namespace + +#undef static_class_name + +#endif + diff --git a/nucleus/library/basis/astring.cpp b/nucleus/library/basis/astring.cpp new file mode 100644 index 00000000..55b9daf8 --- /dev/null +++ b/nucleus/library/basis/astring.cpp @@ -0,0 +1,1096 @@ +/* +* Name : astring +* Author : Chris Koeritz +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "astring.h" +#include "definitions.h" +#include "functions.h" +#include "guards.h" + +#include +#include +#include + +#ifdef __WIN32__ + #undef strcasecmp + #undef strncasecmp + #define strcasecmp strcmpi + #define strncasecmp strnicmp +#endif + +//#define DEBUG_STRING + // uncomment for debugging version. + +#define no_increment + // macro just documents a blank parameter in the code. + +namespace basis { + +const int LONGEST_SPRINTF = 600; // the longest a simple sprintf can be here. + +const char CASE_DIFFERENCE = char('A' - 'a'); + // the measurement of the difference between upper and lower case. + +// this factor is used to bias dynamic sprintfs for cases where the length +// is specified, but the actual string is shorter than that length. +const int MAX_FIELD_FUDGE_FACTOR = 64; + +const abyte empty_char_star[] = { 0 }; + // used to initialize empty strings. + +////////////// + +bool astring_comparator(const astring &a, const astring &b) { return a.equal_to(b); } + +int calculate_proper_length(int repeat) { return negative(repeat)? 1 : repeat + 1; } + +////////////// + +astring::astring() +: c_character_manager(1, empty_char_star), + c_held_string((char * const *)c_character_manager.internal_offset_mem()) +{} + +astring::astring(const base_string &initial) +: c_character_manager(strlen(initial.observe()) + 1, (abyte *)initial.observe()), + c_held_string((char * const *)c_character_manager.internal_offset_mem()) +{} + +astring::astring(char initial, int repeat) +: c_character_manager(calculate_proper_length(repeat)) +{ + if (!initial) initial = ' '; // for nulls, we use spaces. + int new_size = c_character_manager.length() - 1; + memset(c_character_manager.access(), initial, new_size); + c_character_manager.put(new_size, '\0'); + c_held_string = (char * const *)c_character_manager.internal_offset_mem(); +} + +astring::astring(const astring &s1) +: base_string(), + c_character_manager(s1.c_character_manager), + c_held_string((char * const *)c_character_manager.internal_offset_mem()) +{ +} + +astring::astring(const char *initial) +: c_character_manager(calculate_proper_length(initial? int(strlen(initial)) : 0)) +{ + c_character_manager.put(0, '\0'); + if (!initial) return; // bail because there's no string to copy. + strcpy(access(), initial); + c_held_string = (char * const *)c_character_manager.internal_offset_mem(); +} + +astring::astring(special_flag flag, const char *initial, ...) +: c_character_manager(1, empty_char_star), + c_held_string((char * const *)c_character_manager.internal_offset_mem()) +{ + if (!initial) return; + if ( (flag != UNTERMINATED) && (flag != SPRINTF) ) { + operator = (astring(astring::SPRINTF, "unknown flag %d", flag)); + return; + } + + va_list args; + va_start(args, initial); + + if (flag == UNTERMINATED) { + // special process for grabbing a string that has no terminating nil. + int length = va_arg(args, int); // get the length of the string out. + c_character_manager.reset(length, (abyte *)initial); + c_character_manager += abyte(0); + va_end(args); + return; + } + + // only other flag currently supported is sprintf, so we do that... + base_sprintf(initial, args); + va_end(args); +} + +astring::~astring() { c_held_string = NIL; } + +const astring &astring::empty_string() { return bogonic(); } + +void astring::text_form(base_string &state_fill) const { state_fill.assign(*this); } + +int astring::length() const { return c_character_manager.length() - 1; } + +byte_array &astring::get_implementation() { return c_character_manager; } + +char *astring::access() { return (char *)c_character_manager.access(); } + +char astring::get(int index) const { return (char)c_character_manager.get(index); } + +const char *astring::observe() const +{ return (const char *)c_character_manager.observe(); } + +bool astring::equal_to(const equalizable &s2) const +{ + const astring *s2_cast = cast_or_throw(s2, *this); + return comparator(*s2_cast) == 0; +} + +bool astring::less_than(const orderable &s2) const +{ + const astring *s2_cast = dynamic_cast(&s2); + if (!s2_cast) throw "error: astring::<: unknown type"; + return comparator(*s2_cast) < 0; +} + +int astring::comparator(const astring &s2) const +{ return strcmp(observe(), s2.observe()); } + +bool astring::equal_to(const char *that) const +{ return strcmp(observe(), that) == 0; } + +bool astring::contains(const astring &to_find) const +{ return (find(to_find, 0) < 0) ? false : true; } + +astring &astring::operator += (const astring &s1) +{ insert(length(), s1); return *this; } + +void astring::shrink() +{ + astring copy_of_this(observe()); + c_character_manager.swap_contents(copy_of_this.c_character_manager); +} + +astring &astring::sprintf(const char *initial, ...) +{ + va_list args; + va_start(args, initial); + astring &to_return = base_sprintf(initial, args); + va_end(args); + return to_return; +} + +astring &astring::base_sprintf(const char *initial, va_list &args) +{ + reset(); + if (!initial) return *this; // skip null strings. + if (!initial[0]) return *this; // skip empty strings. + + // these accumulate parts of the sprintf format within the loop. + char flag_chars[23], width_chars[23], precision_chars[23], modifier_chars[23]; + + // thanks for the inspiration to k&r page 156. + for (const char *traverser = initial; *traverser; traverser++) { +#ifdef DEBUG_STRING + printf("index=%d, char=%c\n", traverser - initial, *traverser); +#endif + + if (*traverser != '%') { + // not a special character, so just drop it in. + *this += *traverser; + continue; + } + traverser++; // go to the next character. +#ifdef DEBUG_STRING + printf("index=%d, char=%c\n", traverser - initial, *traverser); +#endif + if (*traverser == '%') { + // capture the "%%" style format specifier. + *this += *traverser; + continue; + } + bool failure = false; + // becomes set to true if something didn't match in a necessary area. + + seek_flag(traverser, flag_chars, failure); + if (failure) { + *this += '%'; + *this += flag_chars; + continue; + } + seek_width(traverser, width_chars); + seek_precision(traverser, precision_chars); + seek_modifier(traverser, modifier_chars); + get_type_character(traverser, args, *this, flag_chars, + width_chars, precision_chars, modifier_chars); + } + return *this; +} + +void astring::seek_flag(const char *&traverser, char *flag_chars, bool &failure) +{ + flag_chars[0] = '\0'; + failure = false; + bool keep_going = true; + while (!failure && keep_going) { + switch (*traverser) { + case '-': case '+': case ' ': case '\011': case '#': + flag_chars[strlen(flag_chars) + 1] = '\0'; + flag_chars[strlen(flag_chars)] = *traverser++; + break; + default: + // we found a character that doesn't belong in the flags. + keep_going = false; + break; + } + } +#ifdef DEBUG_STRING + if (strlen(flag_chars)) printf("[flag=%s]\n", flag_chars); + else printf("no flags\n"); +#endif +} + +void astring::seek_width(const char *&traverser, char *width_chars) +{ + width_chars[0] = '\0'; + bool no_more_nums = false; + bool first_num = true; + while (!no_more_nums) { + char wideness[2] = { *traverser, '\0' }; + if (first_num && (wideness[0] == '0')) { + strcpy(width_chars, wideness); + traverser++; + } else if (first_num && (wideness[0] == '*') ) { + strcpy(width_chars, wideness); + traverser++; + no_more_nums = true; + } else if ( (wideness[0] <= '9') && (wideness[0] >= '0') ) { + // a failure? + strcat(width_chars, wideness); + traverser++; + } else no_more_nums = true; + first_num = false; + } +#ifdef DEBUG_STRING + if (strlen(width_chars)) printf("[width=%s]\n", width_chars); + else printf("no widths\n"); +#endif +} + +void astring::seek_precision(const char *&traverser, char *precision_chars) +{ + precision_chars[0] = '\0'; + if (*traverser != '.') return; + strcpy(precision_chars, "."); + traverser++; + bool no_more_nums = false; + bool first_num = true; + while (!no_more_nums) { + char preciseness[2] = { *traverser, '\0' }; + if (first_num && (preciseness[0] == '0')) { + strcat(precision_chars, preciseness); + traverser++; + no_more_nums = true; + } else if (first_num && (preciseness[0] == '*') ) { + strcat(precision_chars, preciseness); + traverser++; + no_more_nums = true; + } else if ( (preciseness[0] <= '9') && (preciseness[0] >= '0') ) { + strcat(precision_chars, preciseness); + traverser++; + } else no_more_nums = true; + first_num = false; + } +#ifdef DEBUG_STRING + if (strlen(precision_chars)) printf("[precis=%s]\n", precision_chars); + else printf("no precision\n"); +#endif +} + +void astring::seek_modifier(const char *&traverser, char *modifier_chars) +{ + modifier_chars[0] = '\0'; + switch (*traverser) { + case 'F': case 'N': case 'h': case 'l': case 'L': { + modifier_chars[strlen(modifier_chars) + 1] = '\0'; + modifier_chars[strlen(modifier_chars)] = *traverser++; + break; + } + } +#ifdef DEBUG_STRING + if (strlen(modifier_chars)) printf("[mod=%s]\n", modifier_chars); + else printf("no modifiers\n"); +#endif +} + +void astring::get_type_character(const char * &traverser, va_list &args, + astring &output_string, const char *flag_chars, const char *width_chars, + const char *precision_chars, const char *modifier_chars) +{ + char formatting[120]; + strcpy(formatting, "%"); + strcat(formatting, flag_chars); + strcat(formatting, width_chars); + strcat(formatting, precision_chars); + strcat(formatting, modifier_chars); + char tmposh[2] = { *traverser, '\0' }; + strcat(formatting, tmposh); +#ifdef DEBUG_STRING + printf("format: %s\n", formatting); +#endif + + enum argument_size { bits_8, bits_16, bits_32, bits_64, bits_80 }; + bool ints_are_32_bits; +#ifdef __WIN32__ + ints_are_32_bits = true; +#elif defined(__OS2__) + ints_are_32_bits = true; +#elif defined(__MSDOS__) + ints_are_32_bits = false; +#elif defined(__WIN32__) + ints_are_32_bits = false; +#else + ints_are_32_bits = true; +#endif + argument_size next_argument; + bool use_dynamic_sprintf = false; // for dynamic printing of strings only. + // get the type character first and ensure it's valid. + switch (*traverser) { + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': + next_argument = bits_16; + if (ints_are_32_bits) next_argument = bits_32; + break; + case 'f': case 'e': case 'g': case 'E': case 'G': + next_argument = bits_64; + break; + case 'c': + next_argument = bits_8; + break; + case 's': + next_argument = bits_32; + use_dynamic_sprintf = true; + break; + case 'n': + next_argument = bits_32; //????? + break; + case 'p': + next_argument = bits_32; //???? + break; + default: + // this is an error; the character is not recognized, so spew out + // any characters accumulated so far as just themselves. +#ifdef DEBUG_STRING + printf("failure in type char: %c\n", *traverser); +#endif + output_string += formatting; + return; + } +/* hmmm: not supported yet. + if (width_chars && (width_chars[0] == '*')) { + } + if (precision_chars && (precision_chars[0] == '*')) { + } +*/ + if (strlen(modifier_chars)) { + switch (modifier_chars[0]) { + case 'N': // near pointer. + next_argument = bits_16; + if (ints_are_32_bits) next_argument = bits_32; + break; + case 'F': // far pointer. + next_argument = bits_32; + break; + case 'h': // short int. + next_argument = bits_16; + break; + case 'l': // long. + next_argument = bits_32; + break; + case 'L': // long double; + next_argument = bits_80; + break; + default: + // a failure has occurred because the modifier character is not + // one of the recognized values. everything is just spewed out. +#ifdef DEBUG_STRING + printf("failure in modifier: %s\n", modifier_chars); +#endif + output_string += formatting; + return; + } + } + // action time: the output string is given a tasty value. + char temp[LONGEST_SPRINTF]; + char *temp2 = NIL; // for dynamic only. + switch (next_argument) { +//hmmm: this switch is where support would need to be added for having two +// arguments (for the '*' case). + case bits_8: case bits_16: + if (ints_are_32_bits) ::sprintf(temp, formatting, va_arg(args, long)); + else ::sprintf(temp, formatting, va_arg(args, int)); + break; + case bits_32: + if (use_dynamic_sprintf) { + // currently we only do dynamic sprintf for strings. + char *to_print = va_arg(args, char *); + // check if it's valid and if we really need to do it dynamically. + if (!to_print) { + // bogus string; put in a complaint. + use_dynamic_sprintf = false; + ::sprintf(temp, "{error:parm=NIL}"); + } else if (strlen(to_print) < LONGEST_SPRINTF - 2) { + // we're within our bounds, plus some safety room, so just do a + // regular sprintf. + use_dynamic_sprintf = false; + ::sprintf(temp, formatting, to_print); + } else { + // it's too long, so we definitely need to do it dynamically. + temp2 = new char[strlen(to_print) + MAX_FIELD_FUDGE_FACTOR]; + ::sprintf(temp2, formatting, to_print); + } + } else ::sprintf(temp, formatting, va_arg(args, void *)); + break; + case bits_64: + ::sprintf(temp, formatting, va_arg(args, double)); + break; + case bits_80: + ::sprintf(temp, formatting, va_arg(args, long double)); + break; + } + if (use_dynamic_sprintf) { + output_string += temp2; + delete [] temp2; + } else output_string += temp; +} + +//hmmm: de-redundify this function, which is identical to the constructor. +void astring::reset(special_flag flag, const char *initial, ...) +{ + reset(); // clear the string out. + if (!initial) return; + if ( (flag != UNTERMINATED) && (flag != SPRINTF) ) { + operator = (astring(astring::SPRINTF, "unknown flag %d", flag)); + return; + } + + va_list args; + va_start(args, initial); + + if (flag == UNTERMINATED) { + // special process for grabbing a string that has no terminating nil. + int length = va_arg(args, int); // get the length of the string out. + c_character_manager.reset(length, (abyte *)initial); + c_character_manager += abyte(0); + va_end(args); + return; + } + + // only other flag currently supported is sprintf, so we do that... + base_sprintf(initial, args); + va_end(args); +} + +void astring::pad(int len, char padding) +{ + if (length() >= len) return; + byte_array pad(len - length()); + memset(pad.access(), padding, pad.length()); + operator += (astring(UNTERMINATED, (char *)pad.observe(), pad.length())); +} + +void astring::trim(int len) +{ + if (length() <= len) return; + zap(len, end()); +} + +astring &astring::operator = (const astring &s1) +{ + if (this != &s1) + c_character_manager = s1.c_character_manager; + return *this; +} + +astring &astring::operator = (const char *s1) +{ + reset(); + *this += s1; + return *this; +} + +void astring::zap(int position1, int position2) +{ + bounds_return(position1, 0, end(), ); + bounds_return(position2, 0, end(), ); + c_character_manager.zap(position1, position2); +} + +void astring::to_lower() +{ + for (int i = 0; i < length(); i++) + if ( (get(i) >= 'A') && (get(i) <= 'Z') ) + c_character_manager.put(i, char(get(i) - CASE_DIFFERENCE)); +} + +void astring::to_upper() +{ + for (int i = 0; i < length(); i++) + if ( (get(i) >= 'a') && (get(i) <= 'z') ) + c_character_manager.put(i, char(get(i) + CASE_DIFFERENCE)); +} + +astring astring::lower() const +{ + astring to_return(*this); + to_return.to_lower(); + return to_return; +} + +astring astring::upper() const +{ + astring to_return(*this); + to_return.to_upper(); + return to_return; +} + +void astring::copy(char *array_to_stuff, int how_many) const +{ + if (!array_to_stuff) return; + array_to_stuff[0] = '\0'; + if ( (how_many <= 0) || (length() <= 0) ) return; + strncpy(array_to_stuff, observe(), minimum(how_many, int(length()))); + array_to_stuff[minimum(how_many, int(length()))] = '\0'; +} + +bool astring::iequals(const astring &that) const +{ return strcasecmp(observe(), that.observe()) == 0; } + +bool astring::iequals(const char *that) const +{ return strcasecmp(observe(), that) == 0; } + +int astring::ifind(char to_find, int position, bool reverse) const +{ return char_find(to_find, position, reverse, false); } + +int astring::find(char to_find, int position, bool reverse) const +{ return char_find(to_find, position, reverse, true); } + +int astring::find_any(const char *to_find, int position, bool reverse) const +{ return char_find_any(to_find, position, reverse, true); } + +int astring::ifind_any(const char *to_find, int position, bool reverse) const +{ return char_find_any(to_find, position, reverse, false); } + +int astring::find_non_match(const char *to_find, int position, + bool reverse) const +{ return char_find_any(to_find, position, reverse, false, true); } + +char simple_lower(char input) +{ + if ( (input <= 'Z') && (input >= 'A') ) return input - CASE_DIFFERENCE; + return input; +} + +int astring::char_find(char to_find, int position, bool reverse, + bool case_sense) const +{ + if (position < 0) return common::OUT_OF_RANGE; + if (position > end()) return common::OUT_OF_RANGE; + if (reverse) { + for (int i = position; i >= 0; i--) { + if (case_sense && (get(i) == to_find)) return i; + else if (simple_lower(get(i)) == simple_lower(to_find)) return i; + } + } else { + if (case_sense) { + const char *const pos = strchr(observe() + position, to_find); + if (pos) return int(pos - observe()); + } else { + for (int i = position; i < length(); i++) + if (simple_lower(get(i)) == simple_lower(to_find)) return i; + } + } + return common::NOT_FOUND; +} + +bool imatches_any(char to_check, const astring &list) +{ + for (int i = 0; i < list.length(); i++) + if (simple_lower(to_check) == simple_lower(list[i])) return true; + return false; +} + +bool matches_any(char to_check, const astring &list) +{ + for (int i = 0; i < list.length(); i++) + if (to_check == list[i]) return true; + return false; +} + +bool matches_none(char to_check, const astring &list) +{ + bool saw_match = false; + for (int i = 0; i < list.length(); i++) + if (to_check == list[i]) { + saw_match = true; + break; + } + return !saw_match; +} + +int astring::char_find_any(const astring &to_find, int position, bool reverse, + bool case_sense, bool invert_find) const +{ + if (position < 0) return common::OUT_OF_RANGE; + if (position > end()) return common::OUT_OF_RANGE; + if (reverse) { + for (int i = position; i >= 0; i--) { + if (!invert_find) { + if (case_sense && matches_any(get(i), to_find)) return i; + else if (imatches_any(get(i), to_find)) return i; + } else { +//printf("rev posn=%d char=%c", i, get(i)); + // case-sensitivity is not used for inverted finds. + if (matches_none(get(i), to_find)) return i; + } + } + } else { + for (int i = position; i < length(); i++) { + if (!invert_find) { + if (case_sense && matches_any(get(i), to_find)) return i; + else if (imatches_any(get(i), to_find)) return i; + } else { + // case-sensitivity is not used for inverted finds. +//printf("fwd posn=%d char=%c", i, get(i)); + if (matches_none(get(i), to_find)) return i; + } + } + } + return common::NOT_FOUND; +} + +int astring::find(const astring &to_find, int posn, bool reverse) const +{ return str_find(to_find, posn, reverse, true); } + +int astring::ifind(const astring &to_find, int posn, bool reverse) const +{ return str_find(to_find, posn, reverse, false); } + +int astring::str_find(const astring &to_find, int posn, bool reverse, + bool case_sense) const +{ + bounds_return(posn, 0, end(), common::OUT_OF_RANGE); + if (!to_find.length()) return common::BAD_INPUT; + + // skip some steps by finding the first place that the first character of + // the string resides in our string. + if (case_sense) + posn = find(to_find[0], posn, reverse); + else posn = ifind(to_find[0], posn, reverse); + if (posn < 0) return common::NOT_FOUND; + +//hmmm: there is a better way to do this loop in terms of the number of +// comparisons performed. knuth morris pratt algorithm? + if (case_sense) { +//hmmm: this could use strncmp too? + if (reverse) { + if (posn > length() - to_find.length()) + posn = length() - to_find.length(); + for (int i = posn; i >= 0; i--) + if (!memcmp((void *)&observe()[i], (void *)to_find.observe(), + to_find.length())) + return i; + } else { + const int find_len = to_find.length(); + const int str_len = length(); + const char first_char = to_find[0]; + bounds_return(posn, 0, str_len - find_len, common::OUT_OF_RANGE); + for (int i = posn - 1; + ( ( (i = find(first_char, i + 1)) >= 0) + && (str_len - i >= find_len) ); no_increment) { + if (!memcmp((void *)&observe()[i], (void *)to_find.observe(), + to_find.length())) + return i; + } + } + } else { + // not case-sensitive. + if (reverse) { + if (posn > length() - to_find.length()) + posn = length() - to_find.length(); + for (int i = posn; i >= 0; i--) + if (!strncasecmp(&observe()[i], to_find.observe(), to_find.length())) + return i; + } else { + bounds_return(posn, 0, length() - to_find.length(), common::OUT_OF_RANGE); + for (int i = posn; i < length() - to_find.length() + 1; i++) + if (!strncasecmp(&observe()[i], to_find.observe(), to_find.length())) + return i; + } + } + return common::NOT_FOUND; +} + +astring astring::operator + (const astring &s1) const +{ + astring to_return(*this); + to_return += s1; + return to_return; +} + +char &astring::operator [] (int position) +{ + if (position < 0) position = 0; + if (position > end()) position = 0; + abyte &found = c_character_manager.use(position); + char &to_return = *((char *)(&found)); + return to_return; +} + +const char &astring::operator [] (int position) const +{ + if (position < 0) position = 0; + if (position > end()) position = 0; + const abyte &found = c_character_manager.get(position); + const char &to_return = *((const char *)(&found)); + return to_return; +} + +int astring::convert(int default_value) const +{ + if (!length()) return default_value; + int to_return; + int fields = sscanf(observe(), "%d", &to_return); + if (fields < 1) return default_value; + return to_return; +} + +long astring::convert(long default_value) const +{ + if (!length()) return default_value; + long to_return; + int fields = sscanf(observe(), "%ld", &to_return); + if (fields < 1) return default_value; + return to_return; +} + +float astring::convert(float default_value) const +{ + if (!length()) return default_value; + float to_return; + int fields = sscanf(observe(), "%f", &to_return); + if (fields < 1) return default_value; + return to_return; +} + +double astring::convert(double default_value) const +{ + if (!length()) return default_value; + double to_return; + int fields = sscanf(observe(), "%lf", &to_return); + if (fields < 1) return default_value; + return to_return; +} + +astring &astring::operator += (const char *s1) +{ + if (!s1) return *this; + int len = length(); + c_character_manager.insert(len, int(strlen(s1))); + memmove((char *)&c_character_manager[len], s1, int(strlen(s1))); + return *this; +} + +astring &astring::operator += (char s1) +{ + int len = length(); + c_character_manager.insert(len, 1); + c_character_manager.put(len, s1); + return *this; +} + +bool astring::compare(const astring &to_compare, int start_first, + int start_second, int count, bool case_sensitive) const +{ + bounds_return(start_first, 0, end(), false); + bounds_return(start_second, 0, to_compare.end(), false); + bounds_return(start_first + count, start_first, length(), false); + bounds_return(start_second + count, start_second, to_compare.length(), false); + + if (!case_sensitive) { + return !strncasecmp(&observe()[start_first], + &to_compare.observe()[start_second], count); + } else { + return !memcmp((void *)&observe()[start_first], + (void *)&to_compare.observe()[start_second], count); + } +} + +/* +int astring::icompare(const char *to_compare, int length_in) const +{ + if (!length_in) return 0; // nothing is equal to nothing. + int real_len = length_in; + // if they're passing a negative length, we use the full length. + if (negative(length_in)) + real_len = length(); + // if we have no length, make the obvious returns now. + int to_compare_len = int(strlen(to_compare)); + if (!real_len) return to_compare_len? -1 : 0; + // if the second string is empty, it's always less than the non-empty. + if (!to_compare_len) return 1; + int to_return = strncasecmp(observe(), to_compare, real_len); + if (negative(length_in) && !to_return && (to_compare_len > length()) ) { + // catch special case for default length when the two are equal except + // that the second string is longer--this means the first is less than + // second, not equal. + return -1; + } else + return to_return; +} +*/ + +/* +bool astring::oy_icompare(const astring &to_compare, int start_first, + int start_second, int count) const +{ + bounds_return(start_first, 0, end(), false); + bounds_return(start_second, 0, to_compare.end(), false); + bounds_return(start_first + count, start_first, length(), false); + bounds_return(start_second + count, start_second, to_compare.length(), false); + const char *actual_first = this->observe() + start_first; + const char *actual_second = to_compare.observe() + start_second; + return !strncasecmp(actual_first, actual_second, count); +} +*/ + +bool astring::substring(astring &target, int start, int bender) const +{ + target.reset(); + if (bender < start) return false; + const int last = end(); // final position that's valid in the string. + bounds_return(start, 0, last, false); + bounds_return(bender, 0, last, false); + target.reset(UNTERMINATED, observe() + start, bender - start + 1); + return true; +} + +astring astring::substring(int start, int end) const +{ + astring to_return; + substring(to_return, start, end); + return to_return; +} + +astring astring::middle(int start, int count) +{ return substring(start, start + count - 1); } + +astring astring::left(int count) +{ return substring(0, count - 1); } + +astring astring::right(int count) +{ return substring(end() - count + 1, end()); } + +void astring::insert(int position, const astring &to_insert) +{ + bounds_return(position, 0, length(), ); + if (this == &to_insert) { + astring copy_of_me(to_insert); + insert(position, copy_of_me); // not recursive because no longer == me. + } else { + c_character_manager.insert(position, to_insert.length()); + c_character_manager.overwrite(position, to_insert.c_character_manager, + to_insert.length()); + } +} + +bool astring::replace(const astring &tag, const astring &replacement) +{ + int where = find(tag); + if (negative(where)) return false; + zap(where, where + tag.end()); + insert(where, replacement); + return true; +} + +bool astring::replace_all(const astring &to_replace, const astring &new_string) +{ + bool did_any = false; + for (int i = 0; i < length(); i++) { + int indy = find(to_replace, i); + if (negative(indy)) break; // get out since there are no more matches. + i = indy; // update our position to where we found the string. + zap(i, i + to_replace.length() - 1); // remove old string. + insert(i, new_string); // plug the new string into the old position. + i += new_string.length() - 1; // jump past what we replaced. + did_any = true; + } + return did_any; +} + +bool astring::replace_all(char to_replace, char new_char) +{ + bool did_any = false; + for (int i = 0; i < length(); i++) { + if (get(i) == to_replace) { + put(i, new_char); + did_any = true; + } + } + return did_any; +} + +bool astring::matches(const astring &match_list, char to_match) +{ + for (int i = 0; i < match_list.length(); i++) + if (to_match == match_list.get(i)) return true; + return false; +} + +void astring::strip(const astring &strip_list, how_to_strip way) +{ + if (way & FROM_FRONT) + while (length() && matches(strip_list, get(0))) + zap(0, 0); + + if (way & FROM_END) + while (length() && matches(strip_list, get(end()))) + zap(end(), end()); +} + +int astring::packed_size() const { return length() + 1; } + +void astring::pack(byte_array &target) const +{ attach(target, (char *)c_character_manager.observe()); } + +bool astring::unpack(byte_array &source) +{ return detach(source, *this); } + +///int astring::icompare(const astring &to_compare, int length_in) const +///{ return icompare(to_compare.observe(), length_in); } + +/* +int astring::slow_strncasecmp(const char *first, const char *second, int length) +{ + int len1 = int(strlen(first)); + int len2 = int(strlen(second)); + if (!length) return 0; // no characters are equal to none. + if (!len1 && !len2) return 0; // equal as empty. + if (!len1 && len2) return -1; // first < second. + if (len1 && !len2) return 1; // first > second. + if (positive(length)) { + len1 = minimum(length, len1); + len2 = minimum(length, len2); + } + for (int i = 0; i < len1; i++) { + if (i > len2 - 1) return 1; // first > second, had more length. + if (simple_lower(first[i]) < simple_lower(second[i])) + return -1; // first < second. + if (simple_lower(first[i]) > simple_lower(second[i])) + return 1; // first > second. + } + // at this point we know second is equal to first up to the length of + // first. + if (len2 > len1) return -1; // second was longer and therefore greater. + return 0; // equal. +} +*/ + +////////////// + +a_sprintf::a_sprintf() : astring() {} + +a_sprintf::a_sprintf(const astring &s) : astring(s) {} + +a_sprintf::a_sprintf(const char *initial, ...) +: astring() +{ + if (!initial) return; + va_list args; + va_start(args, initial); + base_sprintf(initial, args); + va_end(args); +} + +////////////// + +void attach(byte_array &packed_form, const char *to_attach) +{ + const int len = int(strlen(to_attach)); + const int old_pos = packed_form.last(); + packed_form.insert(old_pos + 1, len + 1); + memmove((char *)packed_form.observe() + old_pos + 1, to_attach, len + 1); +} + +bool detach(byte_array &packed_form, astring &to_detach) +{ + if (!packed_form.length()) return false; + // locate the zero termination if possible. + const void *zero_posn = memchr(packed_form.observe(), '\0', + packed_form.length()); + // make sure we could find the zero termination. + if (!zero_posn) { + // nope, never saw a zero. good thing we checked. + to_detach.reset(); + return false; + } + // set the string up using a standard constructor since we found the zero + // position; we know the string constructor will be happy. + to_detach = (char *)packed_form.observe(); + // compute the length of the string we found based on the position of the + // zero character. + int find_len = int((abyte *)zero_posn - packed_form.observe()); + // whack the portion of the array that we consumed. + packed_form.zap(0, find_len); + return true; +} + +////////////// + +// contract fulfillment area. + +base_string &astring::concatenate_string(const base_string &s) +{ + const astring *cast = dynamic_cast(&s); + if (cast) *this += *cast; + else *this += astring(s.observe()); + return *this; +} + +base_string &astring::concatenate_char(char c) +{ + *this += c; + return *this; +} + +base_string &astring::assign(const base_string &s) +{ + const astring *cast = dynamic_cast(&s); + if (cast) *this = *cast; + else *this = astring(s.observe()); + return *this; +} + +base_string &astring::upgrade(const char *s) +{ + *this = s; + return *this; +} + +bool astring::sub_string(base_string &target, int start, int end) const +{ + astring *cast = dynamic_cast(&target); + if (!cast) throw "error: astring::sub_string: unknown type"; + return substring(*cast, start, end); +} + +bool astring::sub_compare(const base_string &to_compare, int start_first, + int start_second, int count, bool case_sensitive) const +{ + const astring *cast = dynamic_cast(&to_compare); + if (cast) return compare(*cast, start_first, start_second, count, case_sensitive); + else return compare(astring(to_compare.observe()), start_first, start_second, + count, case_sensitive); +} + +void astring::insert(int position, const base_string &to_insert) +{ + const astring *cast = dynamic_cast(&to_insert); + if (cast) this->insert(position, *cast); + else this->insert(position, astring(to_insert.observe())); +} + +} //namespace. + diff --git a/nucleus/library/basis/astring.h b/nucleus/library/basis/astring.h new file mode 100644 index 00000000..810e3dd5 --- /dev/null +++ b/nucleus/library/basis/astring.h @@ -0,0 +1,468 @@ +#ifndef ASTRING_CLASS +#define ASTRING_CLASS + +/*****************************************************************************\ +* * +* Name : astring * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "base_string.h" +#include "byte_array.h" +#include "contracts.h" + +#include + +namespace basis { + +//! Provides a dynamically resizable ASCII character string. +/*! + It mimics the standard (char *) type, but provides a slew of helpful + methods as well as enforcing bounds checking on the underlying array. +*/ + +class astring +: public virtual base_string, + public virtual hoople_standard +{ +public: + astring(); + //!< constructs an empty string. + + astring(const char *initial); + //!< constructs a copy of the string passed in "initial". + + astring(char c, int repeat); + //!< constructs a string with "repeat" characters of "c" in it. + /*!< if "c" is the null character (i.e., equal to zero), then the resulting + string will have "repeat" space characters in it. */ + + astring(const astring &s); + //!< Constructs a copy of the string "s". + + astring(const base_string &initial); + //!< constructs a string from the base class. + + enum special_flag { UNTERMINATED = 62, SPRINTF = 84 }; + astring(special_flag way, const char *s, ...); + //!< constructor that sports a few variable parameter constructions. + /*!< + For a flag of "UNTERMINATED", the constructor expects the third + parameter to be an integer, and then it copies that number of + characters from the C-string "s" without assuming that "s" is zero + terminated. + + For a flag of "SPRINTF", a string is constructed using the format specifier + in "s" in a manner similar to the standard library "sprintf" function + (see the standard library for ). If there are no "%" codes in + "s", then the constructor just copies "s" without modification. If "%" + codes are in the character array, then any additional arguments (...) are + interpreted as they would be by sprintf. The length of the + constructed string is tailored to fit the actual contents. If "s" is + null, then the resulting string will be empty. Currently, the "*" + specifier for variable length fields is not supported. */ + + virtual ~astring(); + //!< destroys any storage for the string. + + DEFINE_CLASS_NAME("astring"); + + virtual int comparator(const astring &s2) const; + //!< helps to fulfill orderable contract. + + int length() const; + //!< Returns the current length of the string. + /*!< The length returned does not include the terminating null character + at the end of the string. */ + + int end() const { return length() - 1; } + //!< returns the index of the last (non-null) character in the string. + /*!< If there is no content in the string, then a negative value is + returned. */ + + bool empty() const { return !length(); } + //!< empty() reports if the string is empty, that is, of zero length(). + bool non_empty() const { return !empty(); } + //!< non_empty() reports if the string has some contents. + bool operator ! () const { return empty(); } + //!< the negation operator returns true if the string is empty. + /*!< it can be used in expressions in a readable way, for example: + if (!my_string) { it_is_empty; } */ + bool t() const { return !empty(); } + //!< t() is a shortcut for the string being "true", as in non-empty. + /*!< the logic here is that the string is not false because it's not + empty. for example: if (my_string.t()) { it_is_not_empty; } */ + + static const astring &empty_string(); + //!< useful wherever empty strings are needed, e.g., function defaults. + /*!< note that this is implemented in the opsystem library to avoid bad + issues with static objects mixed into multiple dlls from a static + library. */ + + virtual const char *observe() const; + //!< observes the underlying pointer to the zero-terminated string. + /*!< this does not allow the contents to be modified. this method should + never return NIL. */ + const char *c_str() const { return observe(); } + //!< synonym for observe. mimics the STL method name. + const char *s() const { return observe(); } + //!< synonym for observe. the 's' stands for "string", if that helps. + + virtual char get(int index) const; + //!< a constant peek at the string's internals at the specified index. + + virtual char *access(); + //!< provides access to the actual string held. + /*!< this should never return NIL. be very careful with the returned + pointer: don't destroy or corrupt its contents (e.g., do not mess with + its zero termination). */ + char *c_str() { return access(); } + //!< synonym for access. mimics the STL method. + char *s() { return access(); } + //!< synonym for access. + + char &operator [] (int position); + //!< accesses individual characters in "this" string. + /*!< if the "position" is out of range, the return value is + not meaningful. */ + const char &operator [] (int position) const; + //!< observes individual characters in "this" string. + /*!< if the "position" is out of range, the return value is + not meaningful. */ + + virtual void put(int position, char to_put) { (*this)[position] = to_put; } + //!< stores the character "to_put" at index "position" in the string. + + astring &sprintf(const char *s, ...); + //!< similar to the SPRINTF constructor, but works on an existing string. + /*!< any existing contents in the string are wiped out. */ + + int convert(int default_value) const; + //!< Converts the string into a corresponding integer. + /*!< The conversion starts at index 0 in "this" string, and stores it in + "value". If a valid integer was found, it is returned. otherwise, the + "default_value" is returned. NOTE: be careful of implicit conversions + here; the "default_value" for any of these functions must either be an + object of the exact type needed or must be cast to that type. */ + long convert(long default_value) const; + //!< converts the string to a long integer. + float convert(float default_value) const; + //!< converts the string to a floating point number. + double convert(double default_value) const; + //!< converts the string to a double precision floating point number. + + bool equal_to(const char *that) const; + //!< returns true if "that" is equal to this. + + bool iequals(const astring &that) const; + //!< returns true if this is case-insensitively equal to "that". + bool iequals(const char *that) const; + //!< returns true if this is case-insensitively equal to "that". + + bool compare(const astring &to_compare, int start_first, + int start_second, int count, bool case_sensitive) const; + //!< Compares "this" string with "to_compare". + /*!< The "start_first" is where the comparison begins in "this" string, + and "start_second" where it begins in the "to_compare". The "count" is + the number of characters to compare between the two strings. If either + index is out of range, or "count"-1 + either index is out of range, then + compare returns false. If the strings differ in that range, false is + returned. Only if the strings have identical contents in the range is + true returned. */ + + bool begins(const astring &maybe_prefix) const + { return compare(maybe_prefix, 0, 0, maybe_prefix.length(), true); } + //!< Returns true if "this" string begins with "maybe_prefix". + + bool ibegins(const astring &maybe_prefix) const + { return compare(maybe_prefix, 0, 0, maybe_prefix.length(), false); } + //!< a case-insensitive method similar to begins(). + + //! returns true if this string ends with "maybe_suffix". + bool ends(const astring &maybe_suffix) const { + const int diff = length() - maybe_suffix.length(); + return (diff >= 0) && compare(maybe_suffix, diff, 0, maybe_suffix.length(), true); + } + //!< a case-insensitive method similar to ends(). + bool iends(const astring &maybe_suffix) const { + const int diff = length() - maybe_suffix.length(); + return (diff >= 0) && compare(maybe_suffix, diff, 0, maybe_suffix.length(), false); + } + + astring &operator = (const astring &s); + //!< Sets the contents of this string to "s". + astring &operator = (const char *s); + //!< Sets the contents of this string to "s". + + void reset() { zap(0, end()); } + //!< clears out the contents string. + + void reset(special_flag way, const char *s, ...); + //!< operates like the constructor that takes a 'special_flag'. + + void copy(char *to_stuff, int count) const; + //!< Copies a maximum of "count" characters from this into "to_stuff". + /*!< The target "to_stuff" is a standard C-string. The terminating zero + from this string is also copied. BE CAREFUL: if "count"+1 is greater than + the allocated length of the C-string "to_stuff", then an invalid memory + write will occur. keep in mind that the terminating zero will be put at + position "count" in the C-string if the full "count" of characters are + copied. */ + void stuff(char *to_stuff, int count) const { copy(to_stuff, count); } + //!< a synonym for copy(). + + astring operator + (const astring &s) const; + //!< Returns the concatenation of "this" and "s". + + astring &operator += (const astring &s); + //!< Modifies "this" by concatenating "s" onto it. + + astring &operator += (const char *s); // this is efficient. + //!< synonym for the concatenation operator but uses a char pointer instead. + astring operator + (const char *s) const { return *this + astring(s); } + //!< synonym for the concatenation operator but uses a char pointer instead. + // this method is not efficient. + + astring &operator += (char c); //!< concatenater for single characters. + + int find(char to_find, int position = 0, bool reverse = false) const; + //!< Locates "to_find" in "this". + /*!< find returns the index of "to_find" or "NOT_FOUND". find starts + looking at "position". find returns "OUT_OF_RANGE" if the position is + beyond the bounds of "this". */ + int find(const astring &to_find, int posn = 0, bool reverse = false) const; + //!< finds "to_find" in this string. + + int ifind(char to_find, int position = 0, bool reverse = false) const; + //!< like the find() methods above, but case-insensitive. + int ifind(const astring &to_find, int posn = 0, bool reverse = false) const; + //!< like the find() methods above, but case-insensitive. + + int find_any(const char *to_find, int position = 0, + bool reverse = false) const; + //!< searches for any of the characters in "to_find". + /*!< the first occurrence of any of those is returned, or a negative + number is returned if no matches are found. */ + int ifind_any(const char *to_find, int position = 0, + bool reverse = false) const; + //!< searches case-insensitively for any of the characters in "to_find". + /*!< the first occurrence of any of those is returned, or a negative number + is returned if none are found. */ + int find_non_match(const char *to_find, int position = 0, + bool reverse = false) const; + //!< searches for any character that is not in "to_find" and returns index. + + bool contains(const astring &to_find) const; + //!< Returns true if "to_find" is contained in this string or false if not. + + bool substring(astring &target, int start, int end) const; + //!< a version that stores the substring in an existing "target" string. + + astring substring(int start, int end) const; + //!< Returns the segment of "this" between the indices "start" and "end". + /*!< An empty string is returned if the indices are out of range. */ + + // helper methods similar to other string's choppers. + astring middle(int start, int count); + //!< returns the middle of the string from "start" with "count" characters. + astring left(int count); + //!< returns the left "count" characters from the string. + astring right(int count); + //!< returns the right "count" characters from the string. + + void pad(int length, char padding = ' '); + //!< makes the string "length" characters long. + /*!< this string is padded with the "padding" character if the string is + less than that length initially. */ + void trim(int length); + //!< shortens the string to "length" if it's longer than that. + + void insert(int position, const astring &to_insert); + //!< Copies "to_insert" into "this" at the "position". + /*!< Characters at the index "position" and greater are moved over. */ + virtual void zap(int start, int end); + //!< Deletes the characters between "start" and "end" inclusively. + /*!< C++ array conventions are used (0 through length()-1 are valid). If + either index is out of bounds, then the string is not modified. */ + + void to_lower(); + //!< to_lower modifies "this" by replacing capitals with lower-case. + /*!< every capital letter is replaced with the corresponding lower case + letter (i.e., A becomes a). */ + void to_upper(); + //!< to_upper does the opposite of to_lower (that is, q becomes Q). + astring lower() const; + //!< like to_lower(), but returns a new string rather than modifying this. + astring upper() const; + //!< like to_upper(), but returns a new string rather than modifying this. + + bool replace(const astring &tag, const astring &replacement); + //!< replaces the first occurrence of "tag" text with the "replacement". + /*!< true is returned if the "tag" was actually found and replaced. */ + bool replace_all(char to_replace, char new_char); + //!< changes all occurrences of "to_replace" with "new_char". + bool replace_all(const astring &to_replace, const astring &new_string); + //! changes all occurrences of "to_replace" into "new_string". + + void shrink(); + //!< resizes the string to its minimum possible length. + /*!< this fixes any situations where a null character has been inserted + into the middle of the string. the string is truncated after the first + null charater encountered and its size is corrected. this also repairs + any case where the string was originally longer than it is now. */ + + enum how_to_strip { FROM_FRONT = 1, FROM_END = 2, FROM_BOTH_SIDES = 3 }; + //!< an enumeration describing the strip operations. + + void strip(const astring &strip_list, how_to_strip way = FROM_BOTH_SIDES); + //!< strips all chars from "strip_list" out of "this" given the "way". + + void strip_spaces(how_to_strip way = FROM_BOTH_SIDES) + { strip(" ", way); } + //!< removes excess space characters from string's beginning, end or both. + + void strip_white_spaces(how_to_strip way = FROM_BOTH_SIDES) + { strip(" \t", way); } + //!< like strip_spaces, but includes tabs in the list to strip. + + static bool matches(const astring &match_list, char to_match); + //!< returns true if "to_match" is found in the "match_list" string. + + int packed_size() const; + //!< Reports the size required to pack this string into a byte array. + + void pack(byte_array &target) const; + //!< stores this string in the "target". it can later be unpacked again. + bool unpack(byte_array &source); + //!< retrieves a string (packed with pack()) from "source" into this string. + /*!< note that the string is grabbed from the array destructively; whatever + portion of the byte array was used to store the string will be removed from + the head of the array. */ + +//hmmm: rename this--it is not a simple icompare, but a strncasecmp analogue. +// int icompare(const astring &to_compare, int length = -1) const; + //!< provides a case insensitive comparison routine. + /*!< this uses the best methods available (that is, it uses a system + function if one exists). the string "to_compare" is compared with this + string. if the "length" is negative, then this entire string is compared + with the entire string "to_compare". otherwise, only "length" characters + from this string are compared. if this string is before "to_compare" in + a lexicographic ordering (basically alphabetical), then a negative number + is returned. if this string is after "to_compare", then a positive number + is returned. zero is returned if the two strings are equal for the extent + of interest. */ + +/// int icompare(const char *to_compare, int length = -1) const; + //!< a version of the above for raw character pointers. + +/// static int slow_strncasecmp(const char *first, const char *second, +/// int length = -1); + //!< a replacement for strncasecmp on platforms without them. + /*!< this is slow because it cannot rely on OS methods to perform the + comparison. if the "length" is negative, then the entire string "first" + is compared to "second". otherwise just "length" characters are compared. + this follows the standard library strncasecmp method: the return value can + be in three states: negative, zero and positive. zero means the strings + are identical lexicographically , whereas less than zero means + "this_string" is less than "to_compare" and greater than zero means + "this_string" is greater than "to_compare". */ + + // play-yard for implementing base class requirements. + + // these implement the orderable and equalizable interfaces. + virtual bool equal_to(const equalizable &s2) const; + virtual bool less_than(const orderable &s2) const; + + virtual base_string &concatenate_string(const base_string &s); + virtual base_string &concatenate_char(char c); + virtual base_string &assign(const base_string &s); + virtual base_string &upgrade(const char *s); + virtual bool sub_string(base_string &target, int start, int end) const; + virtual bool sub_compare(const base_string &to_compare, int start_first, + int start_second, int count, bool case_sensitive) const; + virtual void insert(int position, const base_string &to_insert); + virtual void text_form(base_string &state_fill) const; + +private: + byte_array c_character_manager; + //!< hides the real object responsible for implementing much of the class. + + // the real find methods. + int char_find(char to_find, int position, bool reverse, + bool case_sense) const; + // if "invert_find" is true, then non-matches are reported instead of matches. + int char_find_any(const astring &to_find, int position, bool reverse, + bool case_sense, bool invert_find = false) const; + int str_find(const astring &to_find, int posn, bool reverse, + bool case_s) const; + + // the functions below are used in the formatting string constructor. +public: // only for base_sprintf. + astring &base_sprintf(const char *s, va_list &args); +private: + char *const *c_held_string; //!< peeks into the actual pointer for debugging. + + void seek_flag(const char *&traverser, char *flag_chars, bool &failure); + //!< looks for optional flag characters. + void seek_width(const char *&traverser, char *width_chars); + //!< looks for optional width characters. + void seek_precision(const char *&traverser, char *precision_chars); + //!< looks for optional precision characters. + void seek_modifier(const char *&traverser, char *modifier_char); + //!< looks for optional modifier characters. + void get_type_character(const char *&traverser, va_list &args, + astring &output_string, const char *flag_chars, + const char *width_chars, const char *precision_chars, + const char *modifier_chars); + /*!< the required character in a format specifier is either grabbed here or + the other characters are put into the ouput string without formatting. + the "X"_char variables should have been previously gathered by the + seek_"X" functions. */ + + public: byte_array &get_implementation(); private: // for test programs only.... +}; + +////////////// + +//! a_sprintf is a specialization of astring that provides printf style support. +/*! it makes it much easier to call the SPRINTF style constructor but is +otherwise identical to an astring. */ + +class a_sprintf : public astring +{ +public: + a_sprintf(); + a_sprintf(const char *initial, ...); + a_sprintf(const astring &s); +}; + +////////////// + +typedef bool string_comparator_function(const astring &a, const astring &b); + //!< returns true if the strings "a" and "b" are considered equal. + /*!< this provides a prototype for the equality operation, which allows the + notion of equality to be redefined according to a particular function's + implementation. */ + +bool astring_comparator(const astring &a, const astring &b); + //!< implements a string comparator that just does simple astring ==. + +////////////// + +void attach(byte_array &packed_form, const char *to_attach); + //!< Packs a character string "to_attach" into "packed_form". +bool detach(byte_array &packed_form, astring &to_detach); + //!< Unpacks a character string "to_attach" from "packed_form". + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/base_string.h b/nucleus/library/basis/base_string.h new file mode 100644 index 00000000..c2c2cf45 --- /dev/null +++ b/nucleus/library/basis/base_string.h @@ -0,0 +1,98 @@ +#ifndef BASE_STRING_CLASS +#define BASE_STRING_CLASS + +/*****************************************************************************\ +* * +* Name : base_string * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//hmmm: some of these methods could be pulled out to a base_array class. +// that would be a nice further abstraction. + +#include "contracts.h" + +namespace basis { + +//! Defines the base class for all string processing objects in hoople. + +class base_string : public virtual orderable +{ +public: + virtual int length() const = 0; + //!< Returns the current length of the string. + /*!< The length returned does not include the terminating null character + at the end of the string. */ + + virtual const char *observe() const = 0; + //!< observes the underlying pointer to the zero-terminated string. + /*!< this does not allow the contents to be modified. this method should + never return NIL. */ + + virtual char *access() = 0; + //!< provides access to the actual string held. + /*!< this should never return NIL. be very careful with the returned + pointer: don't destroy or corrupt its contents (e.g., do not mess with + its zero termination). */ + + virtual char get(int index) const = 0; + //!< a constant peek at the string's internals at the specified index. + + virtual void put(int position, char to_put) = 0; + //!< stores the character "to_put" at index "position" in the string. + + virtual bool sub_compare(const base_string &to_compare, int start_first, + int start_second, int count, bool case_sensitive) const = 0; + //!< Compares "this" string with "to_compare". + /*!< The "start_first" is where the comparison begins in "this" string, + and "start_second" where it begins in the "to_compare". The "count" is + the number of characters to compare between the two strings. If either + index is out of range, or "count"-1 + either index is out of range, then + compare returns false. If the strings differ in that range, false is + returned. Only if the strings have identical contents in the range is + true returned. If case-sensitive is false, then matches will not require + the caps and lower-case match. */ + + virtual base_string &assign(const base_string &s) = 0; + //!< Sets the contents of this string to "s". + + virtual base_string &upgrade(const char *s) = 0; + //!< Sets the contents of this string to "s". + + virtual void insert(int position, const base_string &to_insert) = 0; + //!< Copies "to_insert" into "this" at the "position". + /*!< Characters at the index "position" and greater are moved over. */ + + virtual void zap(int start, int end) = 0; + //!< Deletes the characters between "start" and "end" inclusively. + /*!< C++ array conventions are used (0 through length()-1 are valid). If + either index is out of bounds, then the string is not modified. */ + + virtual base_string &concatenate_string(const base_string &s) = 0; + //!< Modifies "this" by concatenating "s" onto it. + + virtual base_string &concatenate_char(char c) = 0; + //!< concatenater for single characters. + + virtual bool sub_string(base_string &target, int start, int end) const = 0; + //!< Gets the segment of "this" between the indices "start" and "end". + /*!< false is returned if the range is invalid. */ + + //! sets this string's contents equal to the contents of "to_copy". + /*! this assignment ensures that setting the base class to derived versions will + succeed; otherwise, a base class copy does very little. */ + virtual base_string &operator =(const base_string &to_copy) { return assign(to_copy); } +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/byte_array.h b/nucleus/library/basis/byte_array.h new file mode 100644 index 00000000..cd5239fa --- /dev/null +++ b/nucleus/library/basis/byte_array.h @@ -0,0 +1,132 @@ +#ifndef BYTE_ARRAY_CLASS +#define BYTE_ARRAY_CLASS + +/*****************************************************************************\ +* * +* Name : byte_array * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "array.h" +#include "base_string.h" +#include "contracts.h" +#include "definitions.h" + +#include // for memcmp. + +namespace basis { + +//! A very common template for a dynamic array of bytes. +/*! + byte_array provides a simple wrapper around array, but with the + exponential growth and simple copy modes automatically enabled. + Note that it is almost always best to forward declare byte_arrays in ones + own headers rather than including this header. +*/ + +class byte_array : public array, public virtual orderable +{ +public: + byte_array(int number = 0, const abyte *initial_contents = NIL) + : array(number, initial_contents, SIMPLE_COPY | EXPONE) {} + //!< constructs an array of "number" bytes from "initial_contents". + + byte_array(const byte_array &to_copy) + : root_object(), array(to_copy) {} + //!< constructs an array bytes by copying the "to_copy" array. + + byte_array(const array &to_copy) : array(to_copy) {} + //!< constructs an array bytes by copying the "to_copy" array. + + virtual ~byte_array() {} + + DEFINE_CLASS_NAME("byte_array"); + + //!< returns an array of zero bytes. + /*!< note that this is implemented in the opsystem library to avoid bad + issues with static objects mixed into multiple dlls from a static + library. */ + static const byte_array &empty_array() + { static byte_array g_empty; return g_empty; } + + // these implement the orderable and equalizable interfaces. + virtual bool equal_to(const equalizable &s2) const { + const byte_array *s2_cast = dynamic_cast(&s2); + if (!s2_cast) throw "error: byte_array::==: unknown type"; + return comparator(*s2_cast) == 0; + } + virtual bool less_than(const orderable &s2) const { + const byte_array *s2_cast = dynamic_cast(&s2); + if (!s2_cast) throw "error: byte_array::<: unknown type"; + return comparator(*s2_cast) < 0; + } + + int comparator(const byte_array &s2) const { + return memcmp(observe(), s2.observe(), length()); + } +}; + +////////////// + +//! A base class for objects that can pack into an array of bytes. +/*! + A packable is an abstract object that represents any object that can + be transformed from a potentially deep form into an equivalent flat + form. The flat form is a simple extent of memory stored as bytes. +*/ + +class packable : public virtual root_object +{ +public: + virtual void pack(byte_array &packed_form) const = 0; + //!< Creates a packed form of the packable object in "packed_form". + /*!< This must append to the data in "packed_form" rather than clearing + prior contents. */ + + virtual bool unpack(byte_array &packed_form) = 0; + //!< Restores the packable from the "packed_form". + /*!< This object becomes the unpacked form, and therefore must lose any of + its prior contents that depend on the data in "packed_form". This is up to + the derived unpack function to figure out. The "packed_form" is modified + by extracting all of the pieces that are used for this object; the + remainder stays in "packed_form". true is returned if the unpacking was + successful. */ + + virtual int packed_size() const = 0; + //!< Estimates the space needed for the packed structure. +}; + +////////////// + +// the two templates below can be used to add or remove objects from an array +// of bytes. NOTE: the functions below will only work with objects that are +// already platform-independent. it's better to make structures packable by +// using the attach and detach functions in the "packable" library. + +//! attach_flat() places a copy of "attachment" onto the array of bytes. +template +void attach_flat(byte_array &target, const contents &attachment) +{ target.concatenate(byte_array(sizeof(attachment), (abyte *)&attachment)); } + +//! detach_flat() pulls the "detached" object out of the array of bytes. +template +bool detach_flat(byte_array &source, contents &detached) +{ + if (sizeof(detached) > source.length()) return false; + detached = *(contents *)source.observe(); + source.zap(0, sizeof(detached) - 1); + return true; +} + +} // namespace. + +#endif + diff --git a/nucleus/library/basis/common_outcomes.cpp b/nucleus/library/basis/common_outcomes.cpp new file mode 100644 index 00000000..a30ca268 --- /dev/null +++ b/nucleus/library/basis/common_outcomes.cpp @@ -0,0 +1,54 @@ +/*****************************************************************************\ +* * +* Name : common_outcomes * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "common_outcomes.h" + +namespace basis { + +const char *common::outcome_name(const outcome &to_name) +{ + switch (to_name.value()) { + case OKAY: return "OKAY"; + case NOT_IMPLEMENTED: return "NOT_IMPLEMENTED"; + case OUT_OF_RANGE: return "OUT_OF_RANGE"; + case NOT_FOUND: return "NOT_FOUND"; + case BAD_INPUT: return "BAD_INPUT"; + case BAD_TYPE: return "BAD_TYPE"; + case IS_FULL: return "IS_FULL"; + case IS_EMPTY: return "IS_EMPTY"; + case IS_NEW: return "IS_NEW"; + case EXISTING: return "EXISTING"; + case FAILURE: return "FAILURE"; + case OUT_OF_MEMORY: return "OUT_OF_MEMORY"; + case ACCESS_DENIED: return "ACCESS_DENIED"; + case IN_USE: return "IN_USE"; + case UNINITIALIZED: return "UNINITIALIZED"; + case TIMED_OUT: return "TIMED_OUT"; + case GARBAGE: return "GARBAGE"; + case NO_SPACE: return "NO_SPACE"; + case DISALLOWED: return "DISALLOWED"; + case INCOMPLETE: return "INCOMPLETE"; + case NO_HANDLER: return "NO_HANDLER"; + case NONE_READY: return "NONE_READY"; + case INVALID: return "INVALID"; + case PARTIAL: return "PARTIAL"; + case NO_LICENSE: return "NO_LICENSE"; + case UNEXPECTED: return "UNEXPECTED"; + case ENCRYPTION_MISMATCH: return "ENCRYPTION_MISMATCH"; + default: return "UNKNOWN_OUTCOME"; + } +} + +} // namespace. + diff --git a/nucleus/library/basis/common_outcomes.h b/nucleus/library/basis/common_outcomes.h new file mode 100644 index 00000000..893a5cb2 --- /dev/null +++ b/nucleus/library/basis/common_outcomes.h @@ -0,0 +1,82 @@ +#ifndef COMMON_OUTCOMES_CLASS +#define COMMON_OUTCOMES_CLASS + +/*****************************************************************************\ +* * +* Name : common_outcomes * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "outcome.h" + +namespace basis { + +//! the "common" class defines our common_outcomes. +class common +{ +public: + //! these outcomes are returned by classes that use the basic HOOPLE support. + /*! More complicated classes will need to define their own outcome values to + describe what occurred during the processing of requests. */ + enum outcomes { + DEFINE_API_OUTCOME(OKAY, 0, "Everything is just fine"), + DEFINE_API_OUTCOME(NOT_IMPLEMENTED, -1, + "The invoked method is unimplemented"), + DEFINE_API_OUTCOME(OUT_OF_RANGE, -2, "The value specified was out " + "of bounds"), + DEFINE_API_OUTCOME(NOT_FOUND, -3, "The item sought is not present"), + DEFINE_API_OUTCOME(BAD_INPUT, -4, "Precondition failure--the parameters " + "were inappropriate"), + DEFINE_API_OUTCOME(BAD_TYPE, -5, "The objects are of incompatible types"), + DEFINE_API_OUTCOME(IS_FULL, -6, "There is no room in the storage facility"), + DEFINE_API_OUTCOME(IS_EMPTY, -7, "The container is empty currently"), + DEFINE_API_OUTCOME(IS_NEW, -8, "The item is new"), + DEFINE_API_OUTCOME(EXISTING, -9, "The item was already present"), + DEFINE_API_OUTCOME(FAILURE, -10, "A failure has occurred"), + DEFINE_API_OUTCOME(OUT_OF_MEMORY, -11, "There is not enough memory for the " + "request according to the operating system"), + DEFINE_API_OUTCOME(ACCESS_DENIED, -12, "The request was denied, possibly " + "by the operating system"), + DEFINE_API_OUTCOME(IN_USE, -13, "The object is already in exclusive use"), + DEFINE_API_OUTCOME(UNINITIALIZED, -14, "The object has not been " + "constructed properly"), + DEFINE_API_OUTCOME(TIMED_OUT, -15, "The allowed time has now elapsed"), + DEFINE_API_OUTCOME(GARBAGE, -16, "The request or response has been " + "corrupted"), + DEFINE_API_OUTCOME(NO_SPACE, -17, "A programmatic limit on storage space " + "has been reached"), + DEFINE_API_OUTCOME(DISALLOWED, -18, "The method denied the request"), + DEFINE_API_OUTCOME(INCOMPLETE, -19, "The operation did not finish or the " + "object is not completed"), + DEFINE_API_OUTCOME(NO_HANDLER, -20, "The object type passed in was not " + "understood by the invoked method"), + DEFINE_API_OUTCOME(NONE_READY, -21, "There were no objects available"), + DEFINE_API_OUTCOME(INVALID, -22, "That request or object was invalid"), + DEFINE_API_OUTCOME(PARTIAL, -23, "The request was only partially finished"), + DEFINE_API_OUTCOME(NO_LICENSE, -24, "The software license does not permit" + "this request"), + DEFINE_API_OUTCOME(UNEXPECTED, -25, "This item was unexpected, although " + "not necessarily erroneous"), + DEFINE_API_OUTCOME(ENCRYPTION_MISMATCH, -26, "The request failed due to a " + "mismatch between encryption expected and encryption provided") + }; + + static const char *outcome_name(const outcome &to_name); + //!< Returns a string representation of the outcome "to_name". + /*!< If "to_name" is unknown, because it's not a member of the + common::outcomes enum, then a string reporting that is returned. */ + +}; //class. + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/basis/contracts.h b/nucleus/library/basis/contracts.h new file mode 100644 index 00000000..def88b3e --- /dev/null +++ b/nucleus/library/basis/contracts.h @@ -0,0 +1,188 @@ +#ifndef CONTRACTS_GROUP +#define CONTRACTS_GROUP + +/*****************************************************************************\ +* * +* Name : contracts * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +/*! @file contracts.h + This is a collection of fairly vital interface classes. +*/ + +#include "outcome.h" + +namespace basis { + +// forward declarations. +class base_string; + +////////////// + +//! Defines an attribute base class that supports get and set operations. + +class attribute : public virtual root_object +{ +public: + virtual const root_object &get() const = 0; + virtual void set(const root_object &new_value) = 0; +}; + +////////////// + +//! Base class for object that can tell itself apart from other instances. +class equalizable : public virtual root_object +{ +public: + virtual bool equal_to(const equalizable &s2) const = 0; + //! the virtual method for object equality. + virtual bool operator == (const equalizable &s2) const { return equal_to(s2); } + //! synactic sugar for comparison operators. +}; + +////////////// + +//! A base for objects that can be alphabetically (lexicographically) ordered. + +class orderable : public virtual equalizable +{ +public: + virtual bool less_than(const orderable &s2) const = 0; + //! the virtual method for object ordering. + virtual bool operator < (const orderable &s2) const { return less_than(s2); } + //! synactic sugar for comparison operators. +}; + +////////////// + +//! Provides an abstract base for logging mechanisms. + +class base_logger : public virtual root_object +{ +public: + virtual outcome log(const base_string &info, int filter) = 0; + //!< writes the information in "info" to the logger using the "filter". + /*!< the derived class can interpret the filter appropriately and only + show the "info" if the filter is enabled. */ +}; + +////////////// + +//! Macro for defining a logging filter value. +#define DEFINE_FILTER(NAME, CURRENT_VALUE, INFO_STRING) NAME = CURRENT_VALUE + +//! These filter values are the most basic, and need to be known everywhere. +enum root_logging_filters { + DEFINE_FILTER(NEVER_PRINT, -1, "This diagnostic entry should be dropped and never seen"), + DEFINE_FILTER(ALWAYS_PRINT, 0, "This diagnostic entry will always be shown or recorded") +}; + +////////////// + +//! Interface for a simple form of synchronization. +/*! + Derived classes must provide a locking operation and a corresponding + unlocking operation. +*/ + +class base_synchronizer : public virtual root_object +{ +public: + virtual void establish_lock() = 0; + virtual void repeal_lock() = 0; +}; + +////////////// + +//! A clonable object knows how to make copy of itself. + +class clonable : public virtual root_object +{ +public: + virtual clonable *clone() const = 0; +}; + +////////////// + +//! Root object for any class that knows its own name. +/*! + This is a really vital thing for debugging to be very helpful, and unfortunately it's not + provided by C++. +*/ + +class nameable : public virtual root_object +{ +public: + virtual const char *class_name() const = 0; + //!< Returns the bare name of this class as a constant character pointer. + /*!< The name returned here is supposed to be just a class name and not + provide any more information than that. It is especially important not to + add any syntactic elements like '::' to the name, since a bare alphanumeric + name is expected. */ +}; + +////////////// + +//! A base class for objects that can provide a synopsis of their current state. +/*! + This helps a lot during debugging and possibly even during normal runtime, since it causes + the object to divulge its internal state for viewing in hopefully readable text. +*/ + +class text_formable : public virtual nameable +{ +public: + virtual const char *class_name() const = 0; // forwarded requirement from nameable. + + virtual void text_form(base_string &state_fill) const = 0; + //!< Provides a text view of all the important info owned by this object. + /*!< It is understood that there could be a large amount of information and that this + function might take a relatively long time to complete. */ +}; + +////////////// + +//! the base class of the most easily used and tested objects in the library. +/*! + Each hoople_standard object must know its name, how to print out its data members, and whether + it's the same as another object or not. +*/ + +class hoople_standard : public virtual text_formable, public virtual equalizable +{ +public: + // this is a union class and has no extra behavior beyond its bases. +}; + +////////////// + +//! a base for classes that can stream their contents out to a textual form. + +class text_streamable : public virtual nameable +{ +public: + virtual bool produce(base_string &target) const = 0; + //!< sends the derived class's member data into the "target" in a reversible manner. + /*!< this should use a tagging system of some sort so that not only can the derived class + verify that its type is really right there in the string, but also that it gets all of its + class data and no other data. the "target" will be destructively consumed, and after a + successful call will no longer contain the object's streamed form at its head. */ + virtual bool consume(const base_string &source) = 0; + //!< chows down on a string that supposedly contains a streamed form. + /*!< the derived class must know how to eat just the portion of the string that holds + its data type and no more. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/definitions.h b/nucleus/library/basis/definitions.h new file mode 100644 index 00000000..c76e10ae --- /dev/null +++ b/nucleus/library/basis/definitions.h @@ -0,0 +1,193 @@ +#ifndef DEFINITIONS_GROUP +#define DEFINITIONS_GROUP + +/*****************************************************************************\ +* * +* Name : definitions * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! @file "definitions.h" Constants and objects used throughout HOOPLE. +/*! @file + Defines a set of useful universal constants (for our chosen universe) and + a set of aliases for convenient abstract and concrete data types. + This is the lowest-level header in hoople and should not include any others. +*/ + +namespace basis { + +////////////// + +// Constants... + +//! The value representing a pointer to nothing, or nothing itself. +#define NIL 0 + +//! A fundamental constant measuring the number of bits in a byte. +#define BITS_PER_BYTE 8 + +//! An approximation of the fundamental circular constant. +#define PI_APPROX 3.14159265358 + +////////////// + +// Data Structures & Functions + +//! This macro just eats what it's passed; it marks unused formal parameters. +#define formal(parameter) + +//! A fairly important unit which is seldom defined... +typedef unsigned char abyte; +/* ridiculous! all to shut microsoft up about ambiguous byte definitions, +which seems like a bug. +struct byte { + byte(unsigned char b = 0) : c_data(b) {} +// byte(char b) : c_data(b) {} + operator unsigned char() const { return c_data; } + operator char() const { return c_data; } + operator int() const { return c_data; } + operator unsigned int() const { return c_data; } + operator bool() const { return (bool)c_data; } + byte operator &(byte and_with) { return c_data & and_with; } + byte operator |(byte or_with) { return c_data & or_with; } + unsigned char c_data; +}; +*/ + +#if defined(UNICODE) && defined(__WIN32__) + //! the flexichar type is always appropriate to hold data for win32 calls. + typedef wchar_t flexichar; +#else + // this version simply defangs any conversions. + typedef char flexichar; +#endif + +//! Abbreviated name for unsigned integers. +typedef unsigned int un_int; +//! Abbreviated name for unsigned short integers. +typedef unsigned short un_short; +//! Abbreviated name for unsigned long integers. +typedef unsigned long un_long; + +// some maximum and minimum values that are helpful. +#ifndef MAXINT32 + //! Maximum 32-bit integer value. + #define MAXINT32 0x7fffffff +#endif +#ifndef MININT32 + //! Minimum 32-bit integer value. + #define MININT32 0x80000000 +#endif +#ifndef MAXINT16 + //! Maximum 32-bit integer value. + #define MAXINT16 0x7fff +#endif +#ifndef MININT16 + //! Minimum 32-bit integer value. + #define MININT16 0x8000 +#endif +#ifndef MAXCHAR + //! Maximum byte-based character value. + #define MAXCHAR 0x7f +#endif +#ifndef MINCHAR + //! Minimum byte-based character value. + #define MINCHAR 0x80 +#endif +#ifndef MAXBYTE + //! Maximum unsigned byte value. + #define MAXBYTE 0xff +#endif +#ifndef MINBYTE + //! Minimum unsigned byte value. + #define MINBYTE 0x00 +#endif + +// Provide definitions for integers with platform independent specific sizes. +// Note that these may have to be adjusted for 64 bit platforms. +typedef char int8; +typedef unsigned char uint8; +typedef signed short int16; +typedef unsigned short uint16; +typedef signed int int32; +typedef unsigned int uint32; + +////////////// + +// useful time constants. + +// the _ms suffix indicates that these are measured in milliseconds. +const int SECOND_ms = 1000; //!< Number of milliseconds in a second. +const int MINUTE_ms = 60 * SECOND_ms; //!< Number of milliseconds in a minute. +const int HOUR_ms = 60 * MINUTE_ms; //!< Number of milliseconds in an hour. +const int DAY_ms = 24 * HOUR_ms; //!< Number of milliseconds in a day. + +// the _s suffix indicates that these are measured in seconds. +const int MINUTE_s = 60; //!< Number of seconds in a minute. +const int HOUR_s = 60 * MINUTE_s; //!< Number of seconds in an hour. +const int DAY_s = 24 * HOUR_s; //!< Number of seconds in a day. + +////////////// + +// useful general constants. + +const int KILOBYTE = 1024; //!< Number of bytes in a kilobyte. +const int MEGABYTE = KILOBYTE * KILOBYTE; //!< Number of bytes in a megabyte. +const int GIGABYTE = MEGABYTE * KILOBYTE; //!< Number of bytes in a gigabyte. +const double TERABYTE = double(GIGABYTE) * double(KILOBYTE); +//double TERABYTE() { return double(GIGABYTE) * double(KILOBYTE); } + //!< Number of bytes in a terabyte. +// /*!< Implemented as a function to avoid annoying link errors for double +// floating point constants in some compilers. */ + +////////////// + +// Super basic objects... + +//! lowest level object for all hoople objects. supports run-time type id. + +class root_object +{ +public: + virtual ~root_object() {} +}; + +////////////// + +// compiler specific dumping ground for global settings... + +#ifdef _MSC_VER + // turns off annoying complaints from visual c++. + #pragma warning(disable : 4251 4275 4003 4800 4355 4786 4290 4996 4407) + #pragma warning(error : 4172) + // 4251 and 4275 turn off warnings regarding statically linked code + // not being marked with dll import/export flags. + // 4003 turns off warnings about insufficient number of parameters passed + // to a macro. + // 4800 turns off the warning about conversion from int to bool not being + // efficient. + // 4355 turns off the warning re 'this' used in base member init list. + // 4786 turns off the warning about 'identifier' truncated to 'number' + // characters in the debug information which frequenly happens when + // STL pair and set templates are expanded. + // 4172 is made an error because this warning is emitted for a dangerous + // condition; the address of a local variable is being returned, making + // the returned object junk in almost all cases. + // 4996 turns off warnings about deprecated functions, which are mostly + // bullshit, since these are mainly the core posix functions. +#endif // ms visual c++. + +////////////// + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/basis/enhance_cpp.h b/nucleus/library/basis/enhance_cpp.h new file mode 100644 index 00000000..98589ec7 --- /dev/null +++ b/nucleus/library/basis/enhance_cpp.h @@ -0,0 +1,101 @@ +#ifndef ENHANCE_CPP_GROUP +#define ENHANCE_CPP_GROUP + +/*****************************************************************************\ +* * +* Name : enhance_cpp * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace basis { + +//! Provides missing language features in C++. +/*! + The most noticeable missing thing in C++ when trying to build debugging + and tracking subsystems with it is *reflection*. This header attempts to + ameliorate some of the worst missing parts, such as the fact that a function + cannot get its own name, and other really helpful features. +*/ + +//hmmm: temporary to hide missing code. +#define frame_tracking_instance +#define __trail_of_function(a, b, c, d, e) + +class enhance_cpp : public virtual root_object +{ +public: + // this class is an encapsulator; any macros are not actual members. + +////////////// + + //! Defines the name of a class by providing a couple standard methods. + /*! This provides a virtual function functionality slice for the class name, + as well as a static version that can be used when no instances of the + class exist yet. */ + #define DEFINE_CLASS_NAME(objname) \ + static const char *static_class_name() { return (objname); } \ + virtual const char *class_name() const { return static_class_name(); } + +////////////// + + //! FUNCDEF sets the name of a function (and plugs it into the callstack). + /*! This macro establishes the function name and should be used at the top + of functions that wish to participate in class based logged as well as the + callstack tracing capability of hoople. A new variable is created on the + stack to track the function's presence until the function exits, at which + time the stack will no longer show it as active. */ + #define FUNCDEF(func_in) \ + const char *func = (const char *)func_in; \ + frame_tracking_instance __trail_of_function(static_class_name(), func, \ + __FILE__, __LINE__, true) + +////////////// + + //! A macro used within the FUNCTION macro to do most of the work. + #define BASE_FUNCTION(func) astring just_function = astring(func); \ + astring function_name = static_class_name(); \ + function_name += astring("::") + just_function + //! This macro sets up a descriptive variable called "function_name". + /*! The variable includes the object's name (static_class_name() must be + implemented for the current object) and the current function's name within + that object (the macro "func" must be defined with that name). */ + #define FUNCTION(func) BASE_FUNCTION(func); \ + function_name += ": "; \ + update_current_stack_frame_line_number(__LINE__) + + //! A macro used within the INSTANCE_FUNCTION macro. + #define BASE_INSTANCE_FUNCTION(func) astring just_function = astring(func); \ + astring function_name = instance_name(); \ + function_name += astring("::") + just_function + //! A function macro that contains more information. + /*! This macro is similar to FUNCTION but it uses the class's instance_name() + method (see root_object). The instance function usually will provide more + information about the class. */ + #define INSTANCE_FUNCTION(func) BASE_INSTANCE_FUNCTION(func); \ + function_name += ": "; \ + update_current_stack_frame_line_number(__LINE__) + +////////////// + + //! __WHERE__ is a macro that combines the file and line number macros. + /*! These are available to most compilers as automatically updated macros + called __FILE__ and __LINE__. This macro can be used anywhere an astring can + be used and reports the current file name and line number. */ + #define __WHERE__ basis::a_sprintf("%s [line %d]", __FILE__, __LINE__) + +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/environment.cpp b/nucleus/library/basis/environment.cpp new file mode 100644 index 00000000..5e50abfb --- /dev/null +++ b/nucleus/library/basis/environment.cpp @@ -0,0 +1,87 @@ +////////////// +// Name : environment +// Author : Chris Koeritz +////////////// +// Copyright (c) 1994-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "environment.h" + +#include +#include +#ifdef __UNIX__ + #include + #include +#endif +#ifdef __WIN32__ + #define _WINSOCKAPI_ // make windows.h happy about winsock. + #include + #include +#endif + +namespace basis { + +astring environment::get(const astring &variable_name) +{ +#ifdef __WIN32__ + char *value = getenv(variable_name.upper().observe()); + // dos & os/2 require upper case for the name, so we just do it that way. +#else + char *value = getenv(variable_name.observe()); + // reasonable OSes support mixed-case environment variables. +#endif + astring to_return; + if (value) + to_return = astring(value); + return to_return; +} + +bool environment::set(const astring &variable_name, const astring &value) +{ + int ret = 0; +#ifdef __WIN32__ + astring assignment = variable_name + "=" + value; + ret = _putenv(assignment.s()); +#else + ret = setenv(variable_name.s(), value.s(), true); +#endif + return !ret; +} + +basis::un_int environment::system_uptime() +{ +#ifdef __WIN32__ + return timeGetTime(); +#else + static clock_t __ctps = sysconf(_SC_CLK_TCK); // clock ticks per second. + static const double __multiplier = 1000.0 / double(__ctps); + // the multiplier gives us our full range for the tick counter. + + // read uptime info from the OS. + tms uptime; + basis::un_int real_ticks = times(&uptime); + + // now turn this into the number of milliseconds. + double ticks_up = (double)real_ticks; + ticks_up = ticks_up * __multiplier; // convert to time here. + + // we use the previous version of this calculation, which expected a basis::u_int + // to double conversion to provide a modulo operation rather than just leaving + // the basis::un_int at its maximum value (2^32-1). however, that expectation is not + // guaranteed on some platforms (e.g., ARM processor with floating point + // emulation) and thus it becomes a bug around 49 days and 17 hours into + // OS uptime because the value gets stuck at 2^32-1 and never rolls over. + return basis::un_int(ticks_up); +#endif +} + +} //namespace. + diff --git a/nucleus/library/basis/environment.h b/nucleus/library/basis/environment.h new file mode 100644 index 00000000..e9823e73 --- /dev/null +++ b/nucleus/library/basis/environment.h @@ -0,0 +1,55 @@ +#ifndef ENVIRONMENT_CLASS +#define ENVIRONMENT_CLASS + +////////////// +// Name : environment +// Author : Chris Koeritz +////////////// +// Copyright (c) 1994-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "astring.h" +#include "definitions.h" + +namespace basis { + +//! Provides access to the system's environment variables. + +class environment : public virtual root_object +{ +public: + static astring get(const astring &variable_name); + //!< looks up the "variable_name" in the current environment variables. + /*!< this returns the value for "variable_name" as it was found in the + operating system's environment variables that are defined at this point + in time for the user and process. the returned string will be empty if no + variable under that name could be found. */ + +// static astring get(const char *variable_name) { return get(astring(variable_name)); } + //!< synonym using simpler char pointer. + + static bool set(const astring &variable_name, const astring &value); + //!< adds or creates "variable_name" in the environment. + /*!< changes the current set of environment variables by adding or + modifying the "variable_name". its new value will be "value". */ + +// static bool set(const char *variable_name, const char *value) +// { return set(astring(variable_name), astring(value)); } + //!< synonym using simpler char pointers. + + static basis::un_int system_uptime(); + //!< gives the operating system's uptime in a small form that rolls over. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/functions.h b/nucleus/library/basis/functions.h new file mode 100644 index 00000000..777461a9 --- /dev/null +++ b/nucleus/library/basis/functions.h @@ -0,0 +1,157 @@ +#ifndef FUNCTIONS_GROUP +#define FUNCTIONS_GROUP + +/*****************************************************************************\ +* * +* Name : functions * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +/*! @file functions.h + Provides a set of useful mini-functions. +*/ + +#include "definitions.h" + +namespace basis { + +template type maximum(type a, type b) + { return (a > b)? a : b; } + //!< minimum returns the lesser of two values. +template type minimum(type a, type b) + { return (a < b)? a : b; } + //!< maximum returns the greater of two values. + +template type absolute_value(type a) + { return (a >= 0)? a : -a; } + //!< Returns a if a is non-negative, and returns -a otherwise. + +////////////// + +template bool positive(const type &a) { return a > 0; } + //!< positive returns true if "a" is greater than zero, or false otherwise. +template bool non_positive(const type a) { return a <= 0; } + //!< non_positive returns true if "a" is less than or equal to zero. +template bool negative(const type &a) { return a < 0; } + //!< negative returns true if "a" is less than zero. +template bool non_negative(const type &a) { return a >= 0; } + //!< non_negative returns true if "a" is greater than or equal to zero. + +////////////// + +// the following comparisons are borrowed from the STL. they provide the full set of comparison +// operators for any object that implements the equalizable and orderable base classes. +template +bool operator != (const T1 &x, const T2 &y) { return !(x == y); } + +template +bool operator > (const T1 &x, const T2 &y) { return y < x; } + +template +bool operator <= (const T1 &x, const T2 &y) { return !(y < x); } + +template +bool operator >= (const T1 &x, const T2 &y) { return !(x < y); } + +////////////// + +//! dynamically converts a type to a target type, or throws an exception if it cannot. +template +target_type *cast_or_throw(source_type &to_cast, const target_type &ignored) +{ + if (!&ignored) {} // do nothing. + target_type *cast = dynamic_cast(&to_cast); + if (!cast) throw "error: casting problem, unknown RTTI cast."; + return cast; +} + +//! const version of the cast_or_throw template. +template +const target_type *cast_or_throw(const source_type &to_cast, const target_type &ignored) +{ + if (!&ignored) {} // do nothing. + const target_type *cast = dynamic_cast(&to_cast); + if (!cast) throw "error: casting problem, unknown RTTI cast."; + return cast; +} + +////////////// + +template bool range_check(const type &c, const type &low, + const type &high) { return (c >= low) && (c <= high); } + //!< Returns true if "c" is between "low" and "high" inclusive. + +template type square(const type &a) { return a * a; } + //!< Returns the square of the object (which is a * a). + +template void flip_increasing(type &a, type &b) + { if (b < a) { type tmp = a; a = b; b = tmp; } } + //!< Makes sure that two values are in increasing order (a < b). + +template void flip_decreasing(type &a, type &b) + { if (b > a) { type tmp = a; a = b; b = tmp; } } + //!< Makes sure that two values are in decreasing order (a > b). + +template void swap_values(type &a, type &b) + { type tmp = a; a = b; b = tmp; } + //!< Exchanges the values held by "a" & "b". + +template type sign(type a) + { if (a < 0) return -1; else if (a > 0) return 1; else return 0; } + //!< Returns the numerical sign of a number "a". + +////////////// + +// helpful coding / debugging macros: + +//! deletion with clearing of the pointer. +/*! this function simplifies the two step process of deleting a pointer and +then clearing it to NIL. this makes debugging a bit easier since an access +of NIL should always cause a fault, rather than looking like a possibly +valid object. */ +template +void WHACK(contents * &ptr) { if (ptr) { delete ptr; ptr = NIL; } } + +//! Returns an object that is defined statically. +/*! Thus the returned object will never be recreated once this function +is called within the same scope of memory (within a dynamic library or +application). This is useful for templates that want to have access to a +bogus element whose contents don't matter. NOTE: bogonic is not +thread safe! */ +template type &bogonic() { + static type local_bogon; + return local_bogon; +} + +////////////// + +template +type number_of_packets(type message_size, type packet_size) +{ return message_size / packet_size + ((message_size % packet_size) != 0); } + //!< Reports number of packets needed given a total size and the packet size. + /*!< This returns the number of packets needed to contain a contiguous array + of characters with size "message_size" when the number of characters + per packet is "packet_size". */ + +template +type last_packet_size(type message_size, type packet_size) +{ return message_size % packet_size? message_size % packet_size : packet_size; } + //!< Tells how many bytes are used within last packet. + /*< The companion call to number_of_packets; it returns the size of the last + packet in the sequence of packets, taking into account the special case + where the message_size divides evenly. */ + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/gnu_header.h b/nucleus/library/basis/gnu_header.h new file mode 100644 index 00000000..d78b2a4f --- /dev/null +++ b/nucleus/library/basis/gnu_header.h @@ -0,0 +1,29 @@ +#ifndef {NAME}_CLASS +#define {NAME}_CLASS + +////////////// +// Name : {class name} +// Author : {your name} +////////////// +// Copyright (c) 2010-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +//! brief description goes here. +/*! + detailed description goes here. +*/ + +////////////// +// class definition goes here....... +////////////// + +#endif + diff --git a/nucleus/library/basis/guards.cpp b/nucleus/library/basis/guards.cpp new file mode 100644 index 00000000..5bc81378 --- /dev/null +++ b/nucleus/library/basis/guards.cpp @@ -0,0 +1,47 @@ +////////////// +// Name : guards +// Author : Chris Koeritz +////////////// +// Copyright (c) 1989-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "astring.h" +#include "guards.h" + +namespace basis { + +void format_error(const base_string &class_name, const base_string &func_name, + const base_string &error_message, base_string &to_fill) +{ + astring to_return = class_name; + to_return += "::"; + to_return += func_name; + to_return += ": "; + to_return += error_message; + to_fill = to_return; +} + +void throw_error(const base_string &class_name, const base_string &func_name, + const base_string &error_message) +{ + astring to_throw; + format_error(class_name, func_name, error_message, to_throw); + throw to_throw; +} + +void throw_error(const astring &class_name, const astring &func_name, + const astring &error_message) +{ + throw_error((base_string &)class_name, (base_string &)func_name, (base_string &)error_message); +} + +} //namespace. + diff --git a/nucleus/library/basis/guards.h b/nucleus/library/basis/guards.h new file mode 100644 index 00000000..0f24a908 --- /dev/null +++ b/nucleus/library/basis/guards.h @@ -0,0 +1,92 @@ +#ifndef GUARDS_GROUP +#define GUARDS_GROUP + +////////////// +// Name : guards +// Author : Chris Koeritz +////////////// +// Copyright (c) 1989-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +//! The guards collection helps in testing preconditions and reporting errors. +/*! + It also provides checking of boundary conditions, macros for causing + immediate program exit, and other sentinels for constructing preconditions + and postconditions. +*/ + +namespace basis { + +// forward declaration. +class astring; +class base_string; + +////////////// + +// simpler guards first... + +//! Returns true if the value is within the range specified. +template +bool in_range(const contents &value, const contents &low, const contents &high) +{ return !( (high < low) || (value < low) || (value > high) ); } + +////////////// + +//! Verifies that "value" is between "low" and "high", inclusive. +/*! When the number is not in bounds, the function that is currently executing +returns the "to_return" default provided. "to_return" can be empty for +functions that return void. Note: it is also considered a failure for high to +be less than low. */ +#define bounds_return(value, low, high, to_return) \ + { if (!basis::in_range(value, low, high)) return to_return; } + +////////////// + +//! Verifies that "value" is between "low" and "high", inclusive. +/*! "Value" must be an object for which greater than and less than are defined. +The static_class_name() method and func definition are used to tag the +complaint that is emitted when problems are detected. Note that if +CATCH_ERRORS is defined, then the program is _halted_ if the value is out +of bounds. Otherwise, the "to_return" value is returned. */ +#ifdef CATCH_ERRORS + #define bounds_halt(value, low, high, to_return) { \ + if (((value) < (low)) || ((value) > (high))) { \ + throw_error(basis::astring(static_class_name()), basis::astring(func), \ + basis::astring("value ") + #value \ + + " was not in range " + #low + " to " + #high \ + + " at " + __WHERE__); \ + return to_return; \ + } \ + } +#else + #define bounds_halt(a, b, c, d) bounds_return(a, b, c, d) +#endif + +////////////// + +//! writes a string "to_fill" in a nicely formatted manner using the class and function names. +void format_error(const base_string &class_name, const base_string &func_name, + const base_string &error_message, base_string &to_fill); + +//! throws an error that incorporates the class name and function name. +void throw_error(const base_string &class_name, const base_string &func_name, + const base_string &error_message); + +//! synonym method using astrings for easier char * handling. +void throw_error(const astring &class_name, const astring &func_name, + const astring &error_message); + +////////////// + +} // namespace. + +#endif + diff --git a/nucleus/library/basis/makefile b/nucleus/library/basis/makefile new file mode 100644 index 00000000..fce7c926 --- /dev/null +++ b/nucleus/library/basis/makefile @@ -0,0 +1,10 @@ +include cpp/variables.def + +PROJECT = basis +TYPE = library +SOURCE = astring.cpp common_outcomes.cpp utf_conversion.cpp environment.cpp guards.cpp \ + mutex.cpp +TARGETS = basis.lib + +include cpp/rules.def + diff --git a/nucleus/library/basis/mutex.cpp b/nucleus/library/basis/mutex.cpp new file mode 100644 index 00000000..0bf8b943 --- /dev/null +++ b/nucleus/library/basis/mutex.cpp @@ -0,0 +1,111 @@ +/*****************************************************************************\ +* * +* Name : mutex * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// NOTE: we are explicitly avoiding use of new and delete here because this +// class is needed by our memory allocation object, which would be +// providing the new and delete methods. + +#include "mutex.h" + +#include + +#ifdef __UNIX__ + #include +#endif +#ifdef __WIN32__ + #define _WINSOCKAPI_ // make windows.h happy about winsock. + #include +#endif + +namespace basis { + +mutex::mutex() { construct(); } + +mutex::~mutex() { destruct(); } + +void mutex::establish_lock() { lock(); } + +void mutex::repeal_lock() { unlock(); } + +void mutex::construct() +{ +#ifdef __WIN32__ + c_os_mutex = (CRITICAL_SECTION *)malloc(sizeof(CRITICAL_SECTION)); + InitializeCriticalSection((LPCRITICAL_SECTION)c_os_mutex); +#elif defined(__UNIX__) + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + int ret = -1; +#ifdef __APPLE__ + ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#else + ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); +#endif + if (ret != 0) { +//printf("failed to initialize mutex attributes!\n"); fflush(NIL); + } + c_os_mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init((pthread_mutex_t *)c_os_mutex, &attr); + pthread_mutexattr_destroy(&attr); +#else + #pragma error("no implementation of mutexes for this OS yet!") +#endif +} + +void mutex::destruct() +{ + defang(); +} + +void mutex::defang() +{ + if (!c_os_mutex) return; // already defunct. +#ifdef __WIN32__ + DeleteCriticalSection((LPCRITICAL_SECTION)c_os_mutex); + free(c_os_mutex); +#elif defined(__UNIX__) + pthread_mutex_destroy((pthread_mutex_t *)c_os_mutex); + free(c_os_mutex); +#else + #pragma error("no implementation of mutexes for this OS yet!") +#endif + c_os_mutex = 0; +} + +void mutex::lock() +{ + if (!c_os_mutex) return; +#ifdef __WIN32__ + EnterCriticalSection((LPCRITICAL_SECTION)c_os_mutex); +#elif defined(__UNIX__) + pthread_mutex_lock((pthread_mutex_t *)c_os_mutex); +#else + #pragma error("no implementation of mutexes for this OS yet!") +#endif +} + +void mutex::unlock() +{ + if (!c_os_mutex) return; +#ifdef __WIN32__ + LeaveCriticalSection((LPCRITICAL_SECTION)c_os_mutex); +#elif defined(__UNIX__) + pthread_mutex_unlock((pthread_mutex_t *)c_os_mutex); +#else + #pragma error("no implementation of mutexes for this OS yet!") +#endif +} + +} //namespace. + diff --git a/nucleus/library/basis/mutex.h b/nucleus/library/basis/mutex.h new file mode 100644 index 00000000..1efa08e3 --- /dev/null +++ b/nucleus/library/basis/mutex.h @@ -0,0 +1,137 @@ +#ifndef MUTEX_CLASS +#define MUTEX_CLASS + +/*****************************************************************************\ +* * +* Name : mutex * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "contracts.h" + +//! A simple primitive class that encapsulates OS support for mutual exclusion. +/*! + The word "mutex" is an abbreviation for "mutual exclusion". The mutex + provides a simple synchronization object that supports the programming of + critical sections. It is guaranteed to be safe for threads, but it is only + useful within one application rather than between multiple applications. + The mutex_base is hardly ever used directly; instead the mutex class should + be used. +*/ + +namespace basis { + +class mutex : public virtual base_synchronizer +{ +public: + mutex(); //!< Constructs a new mutex. + + virtual ~mutex(); + //!< Destroys the mutex. It should not be locked upon destruction. + + //! Constructor for use with malloc/free instead of new/delete. + void construct(); + + //! Destructor for use with malloc/free instead of new/delete. + void destruct(); + + void lock(); + //!< Clamps down on the mutex, if possible. + /*!< Otherwise the current thread is blocked until the mutex is unlocked. */ + + void unlock(); + //!< Gives up the possession of the mutex. + + virtual void establish_lock(); + //!< Satisfies base class requirements for locking. + virtual void repeal_lock(); + //!< Satisfies base class requirements for unlocking. + +private: + void *c_os_mutex; //!< OS version of the mutex. + + void defang(); + //!< Removes the underlying OS synchronization primitive. + /*!< This method renders this mutex object inoperable. This is useful + when the reason for the lock has vanished, but the mutex object cannot be + deleted yet. Sometimes it may still be referred to, but there is no + longer any critical section to be protected. */ + + mutex(const mutex &); //!< not allowed. + mutex &operator =(const mutex &); //!< not allowed. +}; + +////////////// + +//! auto_synchronizer simplifies concurrent code by automatically unlocking. +/*! + This relies on the base_synchronizer for the form of the objects that + will provide synchronization. The synchronization object is locked when the + auto_synchronizer is created and it is unlocked when the auto_synchronizer + is destroyed. This is most useful when the auto_synchronizer is an automatic + object; the synchronization lock is grabbed at the point of creation (even + in the middle of a function) and it is released when the function or block + scope exits, thus freeing us of the responsibility of always unlocking the + object before exiting from the critical section. + + More Detail: + The auto_synchronizer provides an easy way to provide nearly idiot-proof + synchronization of functions that share the same locking object. By giving + the synchronizer a working object that's derived from synchronization_base, + its mere construction establishes the lock and its destruction releases the + lock. Thus you can protect a critical section in a function by creating + the auto_synchronizer at the top of the function as an automatic object, or + wherever in the function body is appropriate. When the function exits, the + auto_synchronizer will be destroyed as part of the cleanup and the lock will + be released. If there are multiple synchronization objects in a function, + then be very careful. One must order them appropriately to avoid a deadlock. + + for example: @code + mutex my_lock; // the real synchronization primitive. + ... // lots of program in between. + int calculate_average() { + // our function that must control thread concurrency. + auto_synchronizer syncho(my_lock); // establishes the lock. + ... // lots of stuff done in the function in safety from other threads. + } // end of the function. @endcode + + Note that there was no unlock of the mutex above. Remembering to unlock + synchronization primitives is one of the most troublesome requirements of + programming with multiple threads; the auto_synchronizer can be used in + many situations to automate the release of the lock. +*/ + +class auto_synchronizer +{ +public: + auto_synchronizer(base_synchronizer &locker) : _locker(locker) + { _locker.establish_lock(); } + //!< Construction locks the "locker" object for the current program scope. + /*!< This automatically locks a synchronization object until the current + scope (such as a function or even just a block) is exited, which implements + synchronization without needing multiple unlock calls before every return + statement. */ + + ~auto_synchronizer() { _locker.repeal_lock(); } + //!< Releases the lock as this object goes out of scope. + +private: + base_synchronizer &_locker; //!< the locking object. + + // disallowed. + auto_synchronizer(const auto_synchronizer &locker); + auto_synchronizer &operator =(const auto_synchronizer &locker); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/basis/outcome.h b/nucleus/library/basis/outcome.h new file mode 100644 index 00000000..830ef4d3 --- /dev/null +++ b/nucleus/library/basis/outcome.h @@ -0,0 +1,98 @@ +#ifndef OUTCOME_CLASS +#define OUTCOME_CLASS + +/*****************************************************************************\ +* * +* Name : outcome * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "definitions.h" + +namespace basis { + +//! Outcomes describe the state of completion for an operation. +/*! + These are an extremely simple representation of a non-exceptional exit + value as an integer. The range of expression is from 0 to MAXINT. + Outcomes are meant to represent the category of 'how' the operation + completed; they do not carry along any results of 'what' was produced. +*/ + +class outcome +{ +public: + outcome(int value = 0) : c_outcome_value(value) {} + //!< Represents the completion of an operation as a particular "value". + /*!< The outcomes partition the input space of the operation and represent + the different conclusions possible during processing. Values for outcomes + must be maintained on a system-wide basis as unique identifiers for them + to be meaningful. Note that zero is reserved as a default outcome value. + This usually translates to an outcome of OKAY. */ + + ~outcome() { c_outcome_value = 0; } //!< destructor resets outcome value. + + bool equal_to(const outcome &to_compare) const + { return c_outcome_value == to_compare.c_outcome_value; } + //!< Returns true if this outcome is equal to "to_compare". + /*!< comparisons between outcomes will operate properly provided that + all system-wide outcome values are unique. */ + + bool operator == (int to_compare) const + { return c_outcome_value == to_compare; } + //!< Returns true if this outcome is equal to the integer "to_compare". + + int value() const { return c_outcome_value; } + // +#include +#ifdef CVTUTF_DEBUG + #include +#endif + +namespace basis { + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static Booleano isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +Booleano isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ + +////////////// + +#ifdef __cplusplus + +transcode_to_utf16::transcode_to_utf16(const char *utf8_input) +: _orig_length(int(strlen(utf8_input)) + 1), + _converted(new UTF16[_orig_length + 1]) + // we don't ever expect the string to get longer going to the larger data + // type, so the current length should be enough. +{ + _result = conversionOK; + if (_orig_length == 1) { + // no length, so only provide a blank string. + _converted[0] = 0; + return; + } + memset((abyte *)_converted, 0, 2 * _orig_length); + // we use these temporary pointers since the converter resets the source + // and target pointers to the end of the conversion. the same pattern + // is used in the code below. + const UTF8 *temp_in = (const UTF8 *)utf8_input; + UTF16 *temp_out = _converted; + _result = ConvertUTF8toUTF16(&temp_in, temp_in + _orig_length, + &temp_out, temp_out + _orig_length, lenientConversion); +} + +transcode_to_utf16::transcode_to_utf16(const astring &utf8_input) +: _orig_length(utf8_input.length() + 1), + _converted(new UTF16[_orig_length]) +{ + _result = conversionOK; + if (_orig_length == 1) { + // no length, so only provide a blank string. + _converted[0] = 0; + return; + } + memset((abyte *)_converted, 0, 2 * _orig_length); + const UTF8 *temp_in = (const UTF8 *)utf8_input.observe(); + UTF16 *temp_out = _converted; + _result = ConvertUTF8toUTF16(&temp_in, temp_in + _orig_length, + &temp_out, temp_out + _orig_length, lenientConversion); +} + +transcode_to_utf16::~transcode_to_utf16() +{ + delete [] _converted; + _converted = NIL; +} + +int transcode_to_utf16::length() const +{ return int(wcslen((wchar_t *)_converted)); } + +////////////// + +transcode_to_utf8::transcode_to_utf8(const UTF16 *utf16_input) +: _orig_length(int(wcslen((const wchar_t *)utf16_input))), + _new_length(_orig_length * 2 + _orig_length / 2 + 1), + // this is just an estimate. it may be appropriate most of the time. + // whatever doesn't fit will get truncated. + _converted(new UTF8[_new_length]) +{ + _result = conversionOK; + if (_orig_length == 0) { + // no length, so only provide a blank string. + _converted[0] = 0; + return; + } + memset(_converted, 0, _new_length); + const UTF16 *temp_in = (const UTF16 *)utf16_input; + UTF8 *temp_out = _converted; + _result = ConvertUTF16toUTF8(&temp_in, temp_in + _orig_length, + &temp_out, temp_out + _new_length, lenientConversion); +} + +transcode_to_utf8::transcode_to_utf8(const wchar_t *utf16_input) +: _orig_length(int(wcslen(utf16_input))), + _new_length(_orig_length * 2 + _orig_length / 2 + 1), + // this is just an estimate. it may be appropriate most of the time. + // whatever doesn't fit will get truncated. + _converted(new UTF8[_new_length > 0 ? _new_length : 1]) +{ + _result = conversionOK; + if (_orig_length == 0) { + // no length, so only provide a blank string. + _converted[0] = 0; + return; + } + memset(_converted, 0, _new_length); + const UTF16 *temp_in = (const UTF16 *)utf16_input; + UTF8 *temp_out = _converted; + _result = ConvertUTF16toUTF8(&temp_in, temp_in + _orig_length, + &temp_out, temp_out + _new_length, lenientConversion); +} + +transcode_to_utf8::~transcode_to_utf8() +{ + delete [] _converted; + _converted = NIL; +} + +int transcode_to_utf8::length() const +{ return int(strlen((char *)_converted)); } + +transcode_to_utf8::operator astring() const +{ return astring((char *)_converted); } + +////////////// + +null_transcoder::null_transcoder(const char *utf8_input, bool make_own_copy) +: _make_own_copy(make_own_copy), + _converted(make_own_copy? new UTF8[strlen(utf8_input) + 1] + : (const UTF8 *)utf8_input) +{ + if (_make_own_copy) { + strcpy((char *)_converted, utf8_input); + } +} + +null_transcoder::null_transcoder(const astring &utf8_input, bool make_own_copy) +: _make_own_copy(make_own_copy), + _converted(make_own_copy? new UTF8[utf8_input.length() + 1] + : (const UTF8 *)utf8_input.s()) +{ + if (_make_own_copy) { + strcpy((char *)_converted, utf8_input.s()); + } +} + +int null_transcoder::length() const +{ return int(strlen((char *)_converted)); } + +#endif //_cplusplus + +} //namespace. + diff --git a/nucleus/library/basis/utf_conversion.h b/nucleus/library/basis/utf_conversion.h new file mode 100644 index 00000000..33d436eb --- /dev/null +++ b/nucleus/library/basis/utf_conversion.h @@ -0,0 +1,328 @@ +#ifndef UTF_CONVERSION_GROUP +#define UTF_CONVERSION_GROUP + +/*****************************************************************************\ +* * +* Name : utf_conversion * +* Author : Unicode, Inc. (C conversion functions) * +* Author : Chris Koeritz (C++ conversion classes) * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "astring.h" +#include "definitions.h" + +//! @file utf_conversion.h Support for unicode builds. +/*! @file utf_conversion.h + This file provides conversions between UTF-8 and UTF-16 that are essential + to having the code compile in either the UNICODE or regular ANSI character + builds of win32-based code. +*/ + +// original copyright notice still applies to low-level conversion code: +/* + * Copyright 2001-$now Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +namespace basis { + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ +typedef unsigned long UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Booleano; /* 0 or 1 */ + +//hmmm: this is some really gross stuff below, just because it's got +// so much of its guts hanging out. make a class out of the lower- +// level conversion stuff and hide all the details. + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +#ifdef __cplusplus +extern "C" { +#endif + +ConversionResult ConvertUTF8toUTF16 (const UTF8** sourceStart, + const UTF8* sourceEnd, UTF16** targetStart, UTF16* targetEnd, + ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF8 (const UTF16** sourceStart, + const UTF16* sourceEnd, UTF8** targetStart, UTF8* targetEnd, + ConversionFlags flags); + +ConversionResult ConvertUTF8toUTF32 (const UTF8** sourceStart, + const UTF8* sourceEnd, UTF32** targetStart, UTF32* targetEnd, + ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 (const UTF32** sourceStart, + const UTF32* sourceEnd, UTF8** targetStart, UTF8* targetEnd, + ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF32 (const UTF16** sourceStart, + const UTF16* sourceEnd, UTF32** targetStart, UTF32* targetEnd, + ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF16 (const UTF32** sourceStart, + const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, + ConversionFlags flags); + +Booleano isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + +#ifdef __cplusplus +} //extern +#endif //cplusplus + +////////////// + +#ifdef __cplusplus + +// The following types and macros help to make it irrelevant what kind of +// win32 build is being done. They will adapt as needed to provide the +// types used in system calls. They are rendered harmless for other operating +// systems or for non-Unicode builds; this is especially useful for POSIX +// compliant functions that required Unicode in win32 but not in Unix systems. + +#if defined(UNICODE) +///holding to test wx && defined(__WIN32__) + //! to_unicode_temp() converts to UTF-16 as needed for win32 system calls. + /*! this conversion is only appropriate to use within expressions. it does + not create a permanent object and will go out of scope when the full + expression does. */ + #define to_unicode_temp(s) transcode_to_utf16(s) + //! the from_unicode_temp() macro converts the win32 unicode string to UTF-8. + /*! this has the same contraint as to_unicode_temp; it does not persist + beyond the lifetime of the expression containing it. */ + #define from_unicode_temp(s) transcode_to_utf8(s) + //! to_unicode_persist creates a UTF-16 object with block scope. + /*! the object with "name" that is created by this macro will exist on + the stack until the block containing it is closed. this is needed when + storing unicode strings into windows structure, for example. */ + #define to_unicode_persist(name, s) transcode_to_utf16 name(s) + //! from_unicode_persist creates a UTF-8 object with block scope. + /*! similar to to_unicode_persist, this is used for longer-lived objects. */ + #define from_unicode_persist(name, s) transcode_to_utf8 name(s) +#else + // these versions of the macros simply defang any conversions. + #define to_unicode_temp(s) null_transcoder(s, false) + #define from_unicode_temp(s) null_transcoder(s, false) + #define to_unicode_persist(name, s) null_transcoder name(s, true) + #define from_unicode_persist(name, s) null_transcoder name(s, true) +#endif + +#ifdef _MSC_VER + //! sends UTF-8 information to the diagnostic view in the IDE. + #define TRACE_PRINT(s) TRACE(_T("%s"), to_unicode_temp(s)) +#endif + +////////////// + +// The next two classes support converting a UTF-8 string into a UTF-16 +// string and vice-versa. They hold onto the converted string and provide +// operators that return it. + +//! handles the conversion of UTF-8 format strings into UTF-16. + +class transcode_to_utf16 : public virtual root_object +{ +public: + transcode_to_utf16(const char *utf8_input); + //!< translates the "utf8_input" from UTF-8 into UTF-16 format. + /*!< the resulting memory is held in this object until destruction, which + makes this especially convenient for in-place conversions. */ + + transcode_to_utf16(const astring &utf8_input); + //!< this conversion converts the astring in "utf8_input" into UTF-16. + + virtual ~transcode_to_utf16(); + + int length() const; + //!< reports the length of the double-byte-based converted string. + + operator const UTF16 * () const { return _converted; } + //!< observes the converted version of the string (constant version). + operator UTF16 * () { return _converted; } + //!< accesses the converted version of the string (modifiable version). + operator const flexichar * () const { return (const flexichar *)_converted; } + //!< accesses the converted version of the string (modifiable version). + operator flexichar * () { return (flexichar *)_converted; } + //!< accesses the converted version of the string (modifiable version). + + ConversionResult _result; +private: + int _orig_length; //!< how long is the original argument? + UTF16 *_converted; //!< the double-byte version of the original string. +}; + +////////////// + +//! handles the conversion of UTF-16 format strings into UTF-8. + +class transcode_to_utf8 : public virtual root_object +{ +public: + transcode_to_utf8(const UTF16 *utf16_input); + //!< translates the "utf16_input" from UTF-16 into UTF-8 format. + /*!< the resulting memory is held in this object until destruction, which + makes this especially convenient for in-place conversions. */ + + transcode_to_utf8(const wchar_t *utf16_input); + //!< converts win32 style wide characters to UTF-8. + + virtual ~transcode_to_utf8(); + + int length() const; + //!< reports the length of the byte-based converted string. + + operator const UTF8 * () const { return _converted; } + //!< observes the converted version of the string (constant version). + operator UTF8 * () { return _converted; } + //!< accesses the converted version of the string (modifiable version). + + operator astring() const; + //!< converts the char pointer into an astring object. + + ConversionResult _result; +private: + int _orig_length; //!< how long is the original argument? + int _new_length; //!< what did we predictively allocate for the result? + UTF8 *_converted; //!< the double-byte version of the original string. +}; + +////////////// + +//! provides an interface like the transcoders above, but stays in UTF-8. + +class null_transcoder : public virtual root_object +{ +public: + //! allocates memory for the converted form if "make_own_copy" is true. + null_transcoder(const char *utf8_input, bool make_own_copy); + //! similar, but supports astrings. + null_transcoder(const astring &utf8_input, bool make_own_copy); + ~null_transcoder() { + if (_make_own_copy) delete [] _converted; + _converted = NIL; + } + + int length() const; + operator char * () { return (char *)_converted; } + operator const char * () const { return (const char *)_converted; } + +private: + bool _make_own_copy; + const UTF8 *_converted; +}; + +#endif //cplusplus + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/configuration/application_configuration.cpp b/nucleus/library/configuration/application_configuration.cpp new file mode 100644 index 00000000..a8f463ea --- /dev/null +++ b/nucleus/library/configuration/application_configuration.cpp @@ -0,0 +1,374 @@ +/* +* Name : application_configuration +* Author : Chris Koeritz + +* Copyright (c) 1994-$now By Author. 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 2 of +* the License or (at your option) any later version. This is online at: +* http://www.fsf.org/copyleft/gpl.html +* Please send any updates to: fred@gruntose.com +*/ + +#include "application_configuration.h" +#include "ini_configurator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ + #include + #include +#endif +#ifdef __WIN32__ + #include + #include +#else + #include +#endif +#ifdef __UNIX__ + #include + #include +#endif +#include +#include +#include +#include + +using namespace basis; +using namespace filesystem; +using namespace mathematics; +using namespace structures; +using namespace textual; + +#undef LOG +#define LOG(to_print) printf("%s\n", astring(to_print).s()) + +namespace configuration { + +const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; + // maximum command line that we'll deal with here. + +#ifdef __UNIX__ +astring application_configuration::get_cmdline_from_proc() +{ + FUNCDEF("get_cmdline_from_proc"); + static astring __check_once_app_path; +//hmmm: we want to use a single per app static synch here! + if (__check_once_app_path.length()) return __check_once_app_path; + +#ifdef __APPLE__ + __check_once_app_path = query_for_process_info(); + return __check_once_app_path; +#endif + + // we have not looked this app's name up in the path yet. + a_sprintf cmds_filename("/proc/%d/cmdline", process_id()); + FILE *cmds_file = fopen(cmds_filename.s(), "r"); + if (!cmds_file) { + LOG("failed to open our process's command line file.\n"); + return "unknown"; + } +//hmmm: this would be a lot nicer using a byte filer. + size_t size = 2000; + char *filebuff = new char[size + 1]; + ssize_t chars_read = getline((char **)&filebuff, &size, cmds_file); + // read the first line, giving ample space for how long it might be. + fclose(cmds_file); // drop the file again. + if (!chars_read || negative(chars_read)) { + LOG("failed to get any characters from our process's cmdline file.\n"); + return "unknown"; + } + // copy the buffer into a string, which works great since the entries in the + // command line are all separated by zero characters. + __check_once_app_path = filebuff; + delete [] filebuff; + // clean out quote characters from the name. + for (int i = __check_once_app_path.length() - 1; i >= 0; i--) { + if (__check_once_app_path[i] == '"') __check_once_app_path.zap(i, i); + } + // check if the thing has a path attached to it. if it doesn't, we need to accentuate + // our knowledge about the file. + filename testing(__check_once_app_path); + if (testing.had_directory()) return __check_once_app_path; // all set. + +//hmmm: the below might be better off as a find app in path method, which relies on which. + + // there was no directory component, so we'll try to guess one. + astring temp_filename(environment::get("TMP") + + a_sprintf("/zz_cmdfind.%d", chaos().inclusive(0, 999999999))); + system((astring("which ") + __check_once_app_path + " >" + temp_filename).s()); + FILE *which_file = fopen(temp_filename.s(), "r"); + if (!which_file) { + LOG("failed to open the temporary output from which.\n"); + return "unknown"; + } + // reallocate the file buffer. + size = 2000; + filebuff = new char[size + 1]; + chars_read = getline((char **)&filebuff, &size, which_file); + fclose(which_file); + unlink(temp_filename.s()); + if (!chars_read || negative(chars_read)) { + LOG("failed to get any characters from the which cmd output.\n"); + return "unknown"; + } else { + // we had some luck using 'which' to locate the file, so we'll use this version. + __check_once_app_path = filebuff; + while (parser_bits::is_eol(__check_once_app_path[__check_once_app_path.end()])) { + __check_once_app_path.zap(__check_once_app_path.end(), __check_once_app_path.end()); + } + } + delete [] filebuff; + return __check_once_app_path; // return whatever which told us. +} + +// deprecated; better to use the /proc/pid/cmdline file. +astring application_configuration::query_for_process_info() +{ +// FUNCDEF("query_for_process_info"); + astring to_return = "unknown"; + // we ask the operating system about our process identifier and store + // the results in a temporary file. + chaos rando; + a_sprintf tmpfile("/tmp/proc_name_check_%d_%d.txt", process_id(), + rando.inclusive(0, 128000)); +#ifdef __APPLE__ + a_sprintf cmd("ps -o args=\"\" %d >%s", process_id(), + tmpfile.s()); +#else + a_sprintf cmd("ps h -O \"args\" %d >%s", process_id(), + tmpfile.s()); +#endif + // run the command to locate our process info. + int sysret = system(cmd.s()); + if (negative(sysret)) { + LOG("failed to run ps command to get process info"); + return to_return; + } + // open the output file for reading. + FILE *output = fopen(tmpfile.s(), "r"); + if (!output) { + LOG("failed to open the ps output file"); + return to_return; + } + // read the file's contents into a string buffer. + char buff[MAXIMUM_COMMAND_LINE]; + size_t size_read = fread(buff, 1, MAXIMUM_COMMAND_LINE, output); + if (size_read > 0) { + // success at finding some text in the file at least. + while (size_read > 0) { + const char to_check = buff[size_read - 1]; + if ( !to_check || (to_check == '\r') || (to_check == '\n') + || (to_check == '\t') ) + size_read--; + else break; + } + to_return.reset(astring::UNTERMINATED, buff, size_read); + } else { + // couldn't read anything. + LOG("could not read output of process list"); + } + unlink(tmpfile.s()); + return to_return; +} +#endif + +// used as a return value when the name cannot be determined. +#define SET_BOGUS_NAME(error) { \ + LOG(error); \ + if (output) { \ + fclose(output); \ + unlink(tmpfile.s()); \ + } \ + astring home_dir = env_string("HOME"); \ + to_return = home_dir + "/failed_to_determine.exe"; \ +} + +astring application_configuration::application_name() +{ +// FUNCDEF("application_name"); + astring to_return; +#ifdef __APPLE__ + char buffer[MAX_ABS_PATH] = { '\0' }; + uint32_t buffsize = MAX_ABS_PATH - 1; + _NSGetExecutablePath(buffer, &buffsize); + to_return = (char *)buffer; +#elif __UNIX__ + to_return = get_cmdline_from_proc(); +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetModuleFileName(NIL, low_buff, MAX_ABS_PATH - 1); + astring buff = from_unicode_temp(low_buff); + buff.to_lower(); // we lower-case the name since windows seems to UC it. + to_return = buff; +#else + #pragma error("hmmm: no means of finding app name is implemented.") + SET_BOGUS_NAME("not_implemented_for_this_OS"); +#endif + return to_return; +} + +#if defined(__UNIX__) || defined(__WIN32__) + basis::un_int application_configuration::process_id() { return getpid(); } +#else + #pragma error("hmmm: need process id implementation for this OS!") + basis::un_int application_configuration::process_id() { return 0; } +#endif + +astring application_configuration::current_directory() +{ + astring to_return; +#ifdef __UNIX__ + char buff[MAX_ABS_PATH]; + getcwd(buff, MAX_ABS_PATH - 1); + to_return = buff; +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetCurrentDirectory(MAX_ABS_PATH, low_buff); + to_return = from_unicode_temp(low_buff); +#else + #pragma error("hmmm: need support for current directory on this OS.") + to_return = "."; +#endif + return to_return; +} + +// implement the software product function. +const char *application_configuration::software_product_name() +{ +#ifdef GLOBAL_PRODUCT_NAME + return GLOBAL_PRODUCT_NAME; +#else + return "hoople"; +#endif +} + +astring application_configuration::application_directory() +{ return filename(application_name()).dirname().raw(); } + +structures::version application_configuration::get_OS_version() +{ + version to_return; +#ifdef __UNIX__ + utsname kernel_parms; + uname(&kernel_parms); + to_return = version(kernel_parms.release); +#elif defined(__WIN32__) + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + ::GetVersionEx(&info); + to_return = version(a_sprintf("%u.%u.%u.%u", basis::un_short(info.dwMajorVersion), + basis::un_short(info.dwMinorVersion), basis::un_short(info.dwPlatformId), + basis::un_short(info.dwBuildNumber))); +#else + #pragma error("hmmm: need version info for this OS!") +#endif + return to_return; +} + +////////////// + +const char *PATH_CONFIGURATION_FILENAME() { return "paths.ini"; } + +astring application_configuration::application_configuration_file() +{ + filename cfg_file(application_directory() + "/" + PATH_CONFIGURATION_FILENAME()); + return cfg_file.raw(); +} + +const astring &application_configuration::GLOBAL_SECTION_NAME() { STATIC_STRING("Common"); } + +const astring &application_configuration::LOGGING_FOLDER_NAME() { STATIC_STRING("LogPath"); } + +////////////// + +const int MAX_LOG_PATH = 200; + // the maximum length of the entry stored for the log path. + +/* +astring application_configuration::installation_root() +{ + astring to_return = read_item(LOCAL_FOLDER_NAME()); + if (!to_return) { + // well, if this other guy has a path, we'll give that back. otherwise, + // we don't know what the path should be at all. + to_return = filename(application_configuration_file()).dirname(); + } + return to_return; +} +*/ + +astring application_configuration::get_logging_directory() +{ + // start with the root of our installation. + astring def_log = application_directory(); +///installation_root(); + // add logs directory underneath that. + def_log += "/logs"; + // add the subdirectory for logs. + + // now grab the current value for the name, if any. + astring log_dir = read_item(LOGGING_FOLDER_NAME()); + // get the entry for the logging path. + if (!log_dir) { + // if the entry was absent, we set it. + ini_configurator ini(application_configuration_file(), + ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY); + ini.store(GLOBAL_SECTION_NAME(), LOGGING_FOLDER_NAME(), def_log); + } else { + // they gave us something. let's replace the environment variables + // in their string so we resolve paths and such. + log_dir = parser_bits::substitute_env_vars(log_dir); + } + + // now we make sure the directory exists. + struct stat to_fill; + int stat_ret = stat(log_dir.observe(), &to_fill); + if (stat_ret || !(to_fill.st_mode & S_IFDIR) ) { + // if it's not anything yet or if it's not a directory, then we need + // to create it. +//if it's something besides a directory... should it be deleted? +#ifdef __UNIX__ + int mk_ret = mkdir(log_dir.s(), 0777); +#endif +#ifdef __WIN32__ + int mk_ret = mkdir(log_dir.s()); +#endif + if (mk_ret) return ""; +//can't have a log file if we can't make the directory successfully??? + } + + return log_dir; +} + +astring application_configuration::make_logfile_name(const astring &base_name) +{ return get_logging_directory() + "/" + base_name; } + +///astring application_configuration::core_bin_directory() { return read_item("core_bin"); } + +astring application_configuration::read_item(const astring &key_name) +{ + filename ini_name = application_configuration_file(); + ini_configurator ini(ini_name, ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY); + astring to_return = ini.load(GLOBAL_SECTION_NAME(), key_name, ""); + if (!!to_return) { + // if the string has any length, then we process any environment + // variables found encoded in the value. + to_return = parser_bits::substitute_env_vars(to_return); + } + return to_return; +} + +} // namespace. + diff --git a/nucleus/library/configuration/application_configuration.h b/nucleus/library/configuration/application_configuration.h new file mode 100644 index 00000000..e51bb210 --- /dev/null +++ b/nucleus/library/configuration/application_configuration.h @@ -0,0 +1,132 @@ +#ifndef APPLICATION_CONFIGURATION_GROUP +#define APPLICATION_CONFIGURATION_GROUP + +/*****************************************************************************\ +* * +* Name : path configuration definitions * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace configuration { + +//! Defines installation-specific locations in the file system. + +class application_configuration : public virtual basis::root_object +{ +public: + virtual ~application_configuration() {} + + // these methods are mainly about the application itself. + + static basis::astring application_name(); + //!< returns the full name of the current application. + + static basis::astring application_directory(); + //!< returns the directory name where this application is running from. + + static basis::un_int process_id(); + //!< returns the process id for this task, if that's relevant on the OS. + + // these interface with the operating system. + + static structures::version get_OS_version(); + //!< returns the operating system's version information. + /*!< for linux, this has: major/minor/release/revision as components. + for ms-windows, this has: major/minor/platform_ID/build_number. */ + + static basis::astring current_directory(); + //!< returns the current directory as reported by the operating system. + + // the following are more about the installation than this application... + + static const char *software_product_name(); + //!< This global function is available to the system at large for branding info. + +// static basis::astring installation_root(); + //!< returns the top-level directory of this installation. + /*!< returns the folder containing the application directory (usually) as + well as the other folders of configuration information and fonts and + such needed by this installation. */ + + static const char *APPLICATION_CONFIGURATION_FILENAME(); + //!< the configuration file provides a set of paths needed here. + /*!< this file stores paths that the low-level libraries need for + proper operation. this is just the base filename; the fully qualified + path to the path configuration file is provided below. */ + + static basis::astring application_configuration_file(); + //!< the fully specified path to the main path configuration file. + /*!< the location of this file is determined by the directory where this + executable is running. this is the only configuration file that should + reside there, given the itchy vista compliance needs. */ + +//// static basis::astring core_bin_directory(); + //!< retrieves the core binary directory location from paths.ini. + + static basis::astring get_logging_directory(); + //!< returns the directory where log files will be stored. + + // the following are key names within the main configuration file. + + static const basis::astring &GLOBAL_SECTION_NAME(); + //!< the root section name for our configuration items in the main ini file. + /*!< within the configuration file, there is a section named as above. + this section should only be used to define path configuration entries that + affect the general operation of the system. entries that are specific + to particular programs or subsystems should be contained in their own + section. */ + +/// static const basis::astring &LOCAL_FOLDER_NAME(); + //!< entry name in the config file that points at the installation root. + /*!< this is where all files for this product are stored on "this" machine. */ + + static const basis::astring &LOGGING_FOLDER_NAME(); + //!< the location where the log files for the system are stored. + /*!< this is always considered to be a directory under the local folder. + the make_logfile_name() function (see below) can be used to create a + properly formed filename for logging. */ + + // helper methods. + + static basis::astring read_item(const basis::astring &key_name); + //!< returns the entry listed under the "key_name". + /*!< if the "key_name" is valid for the root configuration file, then this + should always return an appropriate value. a failure is denoted by return + of an empty string. */ + + static basis::astring make_logfile_name(const basis::astring &base_name); + //!< generates an installation appropriate log file name from "base_name". + /*!< creates and returns a full path name for a log file with the + "base_name" specified, using the LOGGING_FOLDER_NAME() entry as the + directory name. if the logging directory does not exist, it is created if + that is possible. the returned name is suitable for logging mechanisms that + need a filename. an empty string is returned on failure to create the + directory. */ + +#ifdef __UNIX__ + #ifdef __APPLE__ + static basis::astring get_cmdline_for_apple(); + #endif + static basis::astring get_cmdline_from_proc(); + //!< retrieves the command line from the /proc hierarchy on linux. + static basis::astring query_for_process_info(); + //!< seeks out process info for a particular process. +#endif +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/configuration/config_watcher.cpp b/nucleus/library/configuration/config_watcher.cpp new file mode 100644 index 00000000..e54b475f --- /dev/null +++ b/nucleus/library/configuration/config_watcher.cpp @@ -0,0 +1,153 @@ +/*****************************************************************************\ +* * +* Name : config_watcher * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2008-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "config_watcher.h" + +#include +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace configuration { + +config_watcher::config_watcher(configurator &to_watch) +: _watching(to_watch), + _current_config(new table_configurator), + _previous_config(new table_configurator) +{ + rescan(); // fill out our lists. +} + +config_watcher::~config_watcher() +{ + WHACK(_current_config); + WHACK(_previous_config); +} + +bool config_watcher::rescan() +{ + // copy the current configuration into our previous config tracker. + *_previous_config = *_current_config; + // clean out any current items held. + _current_config->reset(); + + // iterate across the sections in the watched config. + string_set sects; + _watching.section_set(sects); + for (int sectindy = 0; sectindy < sects.length(); sectindy++) { + // every entry in the current section gets added to our current config. + astring curr_section = sects[sectindy]; + string_table entries; + _watching.get_section(curr_section, entries); + _current_config->put_section(curr_section, entries); + } + + return true; +} + +string_set config_watcher::new_sections() const +{ + string_set before; + _previous_config->section_set(before); + string_set after; + _current_config->section_set(before); + return after - before; +} + +string_set config_watcher::deleted_sections() const +{ + string_set before; + _previous_config->section_set(before); + string_set after; + _current_config->section_set(before); + return before - after; +} + +string_set config_watcher::changed_sections() const +{ + string_set before; + _previous_config->section_set(before); + string_set after; + _current_config->section_set(before); + string_set possible_changes = before.intersection(after); + string_set definite_changes; + for (int i = 0; i < possible_changes.elements(); i++) { + const astring §_name = possible_changes[i]; + string_table previous_section; + _previous_config->get_section(sect_name, previous_section); + string_table current_section; + _current_config->get_section(sect_name, current_section); + if (current_section != previous_section) + definite_changes += sect_name; + } + return definite_changes; +} + +string_set config_watcher::deleted_items(const astring §ion_name) +{ + string_table previous_section; + _previous_config->get_section(section_name, previous_section); + string_set previous_names; + previous_section.names(previous_names); + string_table current_section; + _current_config->get_section(section_name, current_section); + string_set current_names; + current_section.names(current_names); + return previous_names - current_names; +} + +string_set config_watcher::new_items(const astring §ion_name) +{ + string_table previous_section; + _previous_config->get_section(section_name, previous_section); + string_set previous_names; + previous_section.names(previous_names); + string_table current_section; + _current_config->get_section(section_name, current_section); + string_set current_names; + current_section.names(current_names); + return current_names - previous_names; +} + +string_set config_watcher::changed_items(const astring §ion_name) +{ + string_table previous_section; + _previous_config->get_section(section_name, previous_section); + string_set previous_names; + previous_section.names(previous_names); + string_table current_section; + _current_config->get_section(section_name, current_section); + string_set current_names; + current_section.names(current_names); + + string_set possible_changes = current_names.intersection(previous_names); + string_set definite_changes; + for (int i = 0; i < possible_changes.elements(); i++) { + const astring &curr_item = possible_changes[i]; + astring prev_value; + _previous_config->get(section_name, curr_item, prev_value); + astring curr_value; + _current_config->get(section_name, curr_item, curr_value); + if (prev_value != curr_value) + definite_changes += curr_item; + } + return definite_changes; +} + +} //namespace. + + diff --git a/nucleus/library/configuration/config_watcher.h b/nucleus/library/configuration/config_watcher.h new file mode 100644 index 00000000..bc32345c --- /dev/null +++ b/nucleus/library/configuration/config_watcher.h @@ -0,0 +1,68 @@ +#ifndef CONFIG_WATCHER_CLASS +#define CONFIG_WATCHER_CLASS + +/*****************************************************************************\ +* * +* Name : config_watcher * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2008-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" +#include "table_configurator.h" + +#include +#include + +namespace configuration { + +//! an object that watches the contents of a configurator for changes. +/*! + when given a configurator object to check, this will initially build an + exact copy of the contents seen. when asked to look for changes to the + configurator, the previous version is compared with the current state and + any changed sections are reported. +*/ + +class config_watcher +{ +public: + config_watcher(configurator &to_watch); + //!< watches the configurator for changes and tracks them. + /*!< "to_watch" must exist for lifetime of this class. */ + + virtual ~config_watcher(); + + DEFINE_CLASS_NAME("config_watcher"); + + bool rescan(); + //!< updates the configurator snapshot and enables the comparison methods. + /*!< call this before testing the changed() method. */ + + // these lists describe how sections have changed, if at all. + structures::string_set new_sections() const; + structures::string_set deleted_sections() const; + structures::string_set changed_sections() const; + + // methods for comparing changes within sections in the config. + structures::string_set new_items(const basis::astring §ion_name); + structures::string_set deleted_items(const basis::astring §ion_name); + structures::string_set changed_items(const basis::astring §ion_name); + +private: + configurator &_watching; //!< the config object we examine. + table_configurator *_current_config; //!< most current records. + table_configurator *_previous_config; //!< records we saw earlier. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/configlet.cpp b/nucleus/library/configuration/configlet.cpp new file mode 100644 index 00000000..97e324e4 --- /dev/null +++ b/nucleus/library/configuration/configlet.cpp @@ -0,0 +1,177 @@ +/*****************************************************************************\ +* * +* Name : configlet * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configlet.h" +#include "configurator.h" + +#include +#include + +using namespace basis; + +namespace configuration { + +const astring bogus_default = "OOPS: not supposed to ever be seen. d'oh!"; + +////////////// + +configlet::configlet(const astring §ion, const astring &entry) +: _section(new astring(section)), + _entry(new astring(entry)) +{} + +configlet::configlet(const configlet &to_copy) +: _section(new astring(*to_copy._section)), + _entry(new astring(*to_copy._entry)) +{} + +configlet::~configlet() +{ + WHACK(_section); + WHACK(_entry); +} + +configlet &configlet::operator =(const configlet &to_copy) +{ + if (this == &to_copy) return *this; + *_section = *to_copy._section; + *_entry = *to_copy._entry; + return *this; +} + +const astring &configlet::section() const { return *_section; } + +const astring &configlet::entry() const { return *_entry; } + +void configlet::section(const astring &new_section) const +{ *_section = new_section; } + +void configlet::entry(const astring &new_entry) const +{ *_entry = new_entry; } + +////////////// + +string_configlet::string_configlet(const astring §ion, const astring &entry, + const astring ¤t_value, const astring &default_value) +: configlet(section, entry), + _current(new astring(current_value)), + _default(new astring(default_value)) +{} + +string_configlet::string_configlet(const string_configlet &to_copy) +: configlet(to_copy.section(), to_copy.entry()), + _current(new astring(*to_copy._current)), + _default(new astring(*to_copy._default)) +{ +} + +string_configlet::~string_configlet() +{ + WHACK(_current); + WHACK(_default); +} + +string_configlet &string_configlet::operator =(const string_configlet &to_copy) +{ + if (this == &to_copy) return *this; + (configlet &)*this = to_copy; + *_current = *to_copy._current; + *_default = *to_copy._default; + return *this; +} + +const astring &string_configlet::current_value() const { return *_current; } + +const astring &string_configlet::default_value() const { return *_default; } + +void string_configlet::current_value(const astring &new_current) +{ *_current = new_current; } + +void string_configlet::default_value(const astring &new_default) +{ *_default = new_default; } + +bool string_configlet::load(configurator &config) +{ + if (config.get(section(), entry(), *_current)) return true; // success. + // we failed to read the value... + *_current = *_default; + if (config.behavior() == configurator::AUTO_STORE) + config.put(section(), entry(), *_current); + return false; // don't hide that it wasn't there. +} + +bool string_configlet::store(configurator &config) const +{ return config.put(section(), entry(), *_current); } + +configlet *string_configlet::duplicate() const +{ return new string_configlet(section(), entry(), *_current, *_default); } + +////////////// + +int_configlet::int_configlet(const astring §ion, const astring &entry, + int current_value, int default_value) +: configlet(section, entry), + _current(current_value), + _default(default_value) +{ +} + +int_configlet::~int_configlet() {} + +void int_configlet::current_value(int new_current) +{ _current = new_current; } + +bool int_configlet::load(configurator &config) +{ + astring temp; + bool to_return = config.get(section(), entry(), temp); + // just check if it was already there. + int temp_c = config.load(section(), entry(), _default); + current_value(temp_c); + return to_return; +} + +bool int_configlet::store(configurator &config) const +{ return config.store(section(), entry(), _current); } + +configlet *int_configlet::duplicate() const +{ return new int_configlet(*this); } + +////////////// + +bounded_int_configlet::bounded_int_configlet(const astring §ion, + const astring &entry, int current_value, int default_value, + int minimum, int maximum) +: int_configlet(section, entry, current_value, default_value), + _minimum(minimum), + _maximum(maximum) +{ +} + +bounded_int_configlet::~bounded_int_configlet() {} + +void bounded_int_configlet::current_value(int new_current) +{ + if (new_current < _minimum) + new_current = default_value(); + if (new_current > _maximum) + new_current = default_value(); + int_configlet::current_value(new_current); +} + +configlet *bounded_int_configlet::duplicate() const +{ return new bounded_int_configlet(*this); } + +} //namespace. + diff --git a/nucleus/library/configuration/configlet.h b/nucleus/library/configuration/configlet.h new file mode 100644 index 00000000..bd848c92 --- /dev/null +++ b/nucleus/library/configuration/configlet.h @@ -0,0 +1,175 @@ +#ifndef CONFIGLET_CLASS +#define CONFIGLET_CLASS + +/*****************************************************************************\ +* * +* Name : configlet * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" + +#include +#include + +namespace configuration { + +//! Represents an atom of configuration info. +/*! + The configlet has a location in a configuration repository that is defined + by its section and key name. Derived types can also have a value that is + stored in that location. +*/ + +class configlet : public virtual basis::root_object +{ +public: + configlet(const basis::astring §ion, const basis::astring &entry); + //!< creates a configlet that lives in the "section" at the "entry". + configlet(const configlet &to_copy); + + virtual ~configlet(); + + DEFINE_CLASS_NAME("configlet"); + + configlet &operator =(const configlet &to_copy); + + const basis::astring §ion() const; + //!< observes the section of this configlet. + const basis::astring &entry() const; + //!< observes the entry name of this configlet. + + void section(const basis::astring &new_section) const; + //!< modifies the configlet section location. + void entry(const basis::astring &new_entry) const; + //!< modifies the configlet entry name. + + virtual bool load(configurator &config) = 0; + //!< retrieves the configlet's information from the "config". + /*!< true is returned when this is successful. note that false is returned + if the entry was not originally present; if the configurator has the + AUTO_STORE behavior, then we will write out the default value on failure. + the next load() would be a success in that case, but would return the + default. */ + + virtual bool store(configurator &config) const = 0; + //!< writes the configlet's information out to the "config". + + virtual configlet *duplicate() const = 0; + //!< a virtual copy constructor for configlets. + /*!< the returned object will be a new copy of this configlet. */ + +private: + basis::astring *_section; //!< the section name, with whatever form is appropriate. + basis::astring *_entry; //!< the entry name in the native representation. +}; + +////////////// + +//! a string_configlet holds onto a character string value. +/*! + it has a current value, which could change due to updates to the + configuration, and a default value that probably won't change as often. + if the "load" operation fails, the default value will be used. +*/ + +class string_configlet : public configlet +{ +public: + string_configlet(const basis::astring §ion, const basis::astring &entry, + const basis::astring ¤t_value = basis::astring::empty_string(), + const basis::astring &default_value = basis::astring::empty_string()); + string_configlet(const string_configlet &to_copy); + virtual ~string_configlet(); + + string_configlet &operator =(const string_configlet &to_copy); + + const basis::astring ¤t_value() const; + const basis::astring &default_value() const; + + void current_value(const basis::astring &new_current); + void default_value(const basis::astring &new_default); + + virtual bool load(configurator &config); + virtual bool store(configurator &config) const; + + configlet *duplicate() const; + +private: + basis::astring *_current; + basis::astring *_default; +}; + +////////////// + +//! Stores a simple integer in a configuration repository. + +class int_configlet : public configlet +{ +public: + int_configlet(const basis::astring §ion, const basis::astring &entry, + int current_value = 0, int default_value = 0); + virtual ~int_configlet(); + + int current_value() const { return _current; } + + virtual void current_value(int new_current); + //!< the modifier function is virtual so derived classes can extend. + + int default_value() const { return _default; } + void default_value(int new_default) { _default = new_default; } + + virtual bool load(configurator &config); + virtual bool store(configurator &config) const; + + configlet *duplicate() const; + +private: + int _current; + int _default; +}; + +////////////// + +//! Stores an integer in a configuration repository with range checking. +/*! + a bounded_int_configlet has current and default values but also specifies a + valid range for the current value. if the current value falls outside + of that range (even via a "set" operation), then the default value is + used for the current. +*/ + +class bounded_int_configlet : public int_configlet +{ +public: + bounded_int_configlet(const basis::astring §ion, const basis::astring &entry, + int current_value, int default_value, int minimum, int maximum); + virtual ~bounded_int_configlet(); + + virtual void current_value(int new_current); + + int minimum() const { return _minimum; } + int maximum() const { return _maximum; } + + void minimum(int new_min) { _minimum = new_min; } + void maximum(int new_max) { _maximum = new_max; } + + configlet *duplicate() const; + +private: + int _minimum; + int _maximum; +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/configurable.h b/nucleus/library/configuration/configurable.h new file mode 100644 index 00000000..c40f54f7 --- /dev/null +++ b/nucleus/library/configuration/configurable.h @@ -0,0 +1,57 @@ +#ifndef CONFIGURABLE_CLASS +#define CONFIGURABLE_CLASS + +/*****************************************************************************\ +* * +* Name : configurable * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! base class for objects that support configuration_list updates. +/*! + The configuration_list implements a set of configuration items and it can + be used to represent a "delta" between the current configuration of an + object and a new configuration that is desired. Objects based on the + configurable class support taking that delta chunk of configuration info + and adapting their current internal configuration to meet the request, if + possible. These objects also support querying their current configuration, + which reports all configuration item names and current settings. +*/ + +#include + + +// forward. +class configuration_list; + +class configurable : public virtual root_object +{ +public: + virtual ~configurable() {} + + DEFINE_CLASS_NAME("configurable"); + + virtual void get_config(configuration_list &to_fill, bool append) const = 0; + //!< interprets the contents of this object as a configuration list. + /*!< the list of configlets can be stored in any configurator object. + if the "append" flag is true, then the list is added to. otherwise it + is cleared first. this method can also be used to retrieve the configlets + that this class defines as its configuration interface. that list can + then be filled in using a configurator and passed to set_config(). */ + + virtual bool set_config(const configuration_list &to_use) = 0; + //!< retrieves the config items from "to_use" and stores them here. + /*!< false is returned if any of the key items are missing or if the + new key cannot be decoded. */ +}; + +#endif + diff --git a/nucleus/library/configuration/configuration_list.cpp b/nucleus/library/configuration/configuration_list.cpp new file mode 100644 index 00000000..97b79650 --- /dev/null +++ b/nucleus/library/configuration/configuration_list.cpp @@ -0,0 +1,97 @@ +/*****************************************************************************\ +* * +* Name : configuration_list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configlet.h" +#include "configuration_list.h" + +#include +#include + +#include + +using namespace basis; +using namespace structures; + +namespace configuration { + +class cl_figlet_list : public amorph {}; + +////////////// + +configuration_list::configuration_list() +: _figs(new cl_figlet_list) +{ +} + +configuration_list::~configuration_list() +{ + WHACK(_figs); +} + +void configuration_list::reset() { _figs->reset(); } + +void configuration_list::add(const configlet &new_item) +{ + zap(new_item); + _figs->append(new_item.duplicate()); +} + +const configlet *configuration_list::find(const configlet &to_find) const +{ + for (int i = 0; i < _figs->elements(); i++) { + configlet &curr = *_figs->borrow(i); + if ( (to_find.section() == curr.section()) + && (to_find.entry() == curr.entry()) + && (typeid(curr) == typeid(to_find)) ) { + return &curr; + } + } + return NIL; +} + +bool configuration_list::zap(const configlet &dead_item) +{ + for (int i = 0; i < _figs->elements(); i++) { + configlet &curr = *_figs->borrow(i); + if ( (dead_item.section() == curr.section()) + && (dead_item.entry() == curr.entry()) ) { + _figs->zap(i, i); + return true; + } + } + return false; +} + +bool configuration_list::load(configurator &config) +{ + bool to_return = true; + for (int i = 0; i < _figs->elements(); i++) { + configlet &curr = *_figs->borrow(i); + if (!curr.load(config)) to_return = false; // any failure is bad. + } + return to_return; +} + +bool configuration_list::store(configurator &config) const +{ + bool to_return = true; + for (int i = 0; i < _figs->elements(); i++) { + configlet &curr = *_figs->borrow(i); + if (!curr.store(config)) to_return = false; // any failure is bad. + } + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/configuration/configuration_list.h b/nucleus/library/configuration/configuration_list.h new file mode 100644 index 00000000..0aea0ca7 --- /dev/null +++ b/nucleus/library/configuration/configuration_list.h @@ -0,0 +1,70 @@ +#ifndef CONFIGURATION_LIST_CLASS +#define CONFIGURATION_LIST_CLASS + +/*****************************************************************************\ +* * +* Name : configuration_list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace configuration { + +// forward. +class cl_figlet_list; +class configlet; +#include + +//! Manages a collection of configlet objects. +/*! + This class provides the ability to operate on the collection of configlets + as a whole. They can be retrieved from or stored to a configurator object. +*/ + +class configuration_list : public virtual basis::root_object +{ +public: + configuration_list(); + virtual ~configuration_list(); + + DEFINE_CLASS_NAME("configuration_list"); + + void reset(); //!< removes all items from the list. + + void add(const configlet &new_item); + //!< adds another configuration atom into the list. + + const configlet *find(const configlet &to_find) const; + //!< locates the actual configlet with the section and entry of "to_find". + /*!< note that this might fail if no matching section and entry are found, + thus returning NIL. the returned object is still kept in the list, so + do not try to destroy it. also note that the object passed in must be + the same type as the object to be found; otherwise, NIL will be + returned. */ + + bool zap(const configlet &dead_item); + //!< removes a previously added configuration item. + /*!< the "dead_item" need only provide the section and entry names. */ + + //! reads the values of all the configlets stored in "config" into this. + bool load(configurator &config); + //! writes the current values of all the configlets in "this" into "config". + bool store(configurator &config) const; + +private: + cl_figlet_list *_figs; //!< our list of configlets. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/configurator.cpp b/nucleus/library/configuration/configurator.cpp new file mode 100644 index 00000000..57cab64c --- /dev/null +++ b/nucleus/library/configuration/configurator.cpp @@ -0,0 +1,82 @@ +/*****************************************************************************\ +* * +* Name : configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" + +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace configuration { + +configurator::~configurator() {} + +astring configurator::load(const astring §ion, const astring &entry, + const astring &default_string) +{ + astring to_return; + if (!get(section, entry, to_return)) { + to_return = default_string; + if (_behavior == AUTO_STORE) put(section, entry, to_return); + // save the entry back if we're in autostore mode. + } + return to_return; +} + +bool configurator::store(const astring §ion, const astring &entry, + const astring &to_store) +{ return put(section, entry, to_store); } + +bool configurator::store(const astring §ion, const astring &entry, + int value) +{ + return store(section, entry, astring(astring::SPRINTF, "%d", value)); +} + +void configurator::sections(string_array &list) +{ + // default implementation does nothing. + list = string_array(); +} + +void configurator::section_set(string_set &list) +{ + string_array temp; + sections(temp); + list = temp; +} + +int configurator::load(const astring §ion, const astring &entry, + int def_value) +{ + astring value_string; + if (!get(section, entry, value_string)) { + if (_behavior == AUTO_STORE) store(section, entry, def_value); + return def_value; + } + return value_string.convert(def_value); +} + +bool configurator::section_exists(const astring §ion) +{ + string_table infos; + // heavy-weight call here... + return get_section(section, infos); +} + +} //namespace. + diff --git a/nucleus/library/configuration/configurator.h b/nucleus/library/configuration/configurator.h new file mode 100644 index 00000000..6286a7c8 --- /dev/null +++ b/nucleus/library/configuration/configurator.h @@ -0,0 +1,127 @@ +#ifndef CONFIGURATOR_CLASS +#define CONFIGURATOR_CLASS + +/*****************************************************************************\ +* * +* Name : configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +namespace configuration { + +//! Provides a base class for configuration repositories. +/*! + All items that can be stored are modelled as having an entry name and a + value. Groups of entries are stored in sections, in which the data + usually have some relation to each other or share a common purpose. +*/ + +class configurator : public virtual basis::root_object +{ +public: + enum treatment_of_defaults { AUTO_STORE, RETURN_ONLY }; + //!< Governs how missing items are treated. + /*!< When the default value is used in the get() method below, it can + either be written to the ini file automatically (AUTO_STORE) or it can + just be returned (RETURN_ONLY). */ + + configurator(treatment_of_defaults behavior = RETURN_ONLY) : _behavior(behavior) {} + virtual ~configurator(); + + //! observes the behavior chosen for the load() function. + treatment_of_defaults behavior() const { return _behavior; } + //! modifies the behavior of the load() function. + void behavior(treatment_of_defaults new_behavior) { + _behavior = (new_behavior == RETURN_ONLY)? RETURN_ONLY : AUTO_STORE; + } + + virtual bool get(const basis::astring §ion, const basis::astring &entry, + basis::astring &found) = 0; + //!< Retrieves an item from the configuration store. + /*!< This retrieves a string into "found" that is listed in the "section" + specified under the "entry" name. if the string is not found, false is + returned. */ + + virtual bool put(const basis::astring §ion, const basis::astring &entry, + const basis::astring &to_store) = 0; + //!< Places an item into the configuration store. + /*!< places an entry into the "section" under the "entry" name using the + string "to_store". if the storage was successful, true is returned. + reasons for failure depend on the derived class implementations. */ + + bool store(const basis::astring §ion, const basis::astring &entry, + const basis::astring &to_store); + //!< a synonym for put. + + //! a synonym for get that implements the auto-store behavior. + /*! if the behavior is set to auto-store, then the default value will be + written when no value existed prior to the load() invocation. */ + basis::astring load(const basis::astring §ion, const basis::astring &entry, + const basis::astring &default_value); + + //! stores an integer value from the configuration store. + bool store(const basis::astring §ion, const basis::astring &entry, int value); + //! loads an integer value from the configuration store. + int load(const basis::astring §ion, const basis::astring &entry, int def_value); + + // the various methods below that operate on sections and entries might not + // be provided by all configurators. that is why there are empty default + // (or simplistic and slow) implementations provided below. + + virtual void sections(structures::string_array &list); + //!< retrieves the section names into "list". + + void section_set(structures::string_set &list); + //!< similar to above, but stores section names into a set. + /*!< this never needs to be overridden; it's simply a set instead + of an array. the real sections method is above for string_array. */ + + virtual bool delete_entry(const basis::astring & formal(section), + const basis::astring & formal(entry)) { return false; } + //!< eliminates the entry specified by the "section" and "entry" name. + + virtual bool delete_section(const basis::astring & formal(section) ) + { return false; } + //!< whacks the entire "section" specified. + + virtual bool section_exists(const basis::astring §ion); + //!< returns true if the "section" is found in the configurator. + /*!< the default implementation is quite slow; if there is a swifter means + for a particular type of configurator, then this should be overridden. */ + + virtual bool get_section(const basis::astring & formal(section), + structures::string_table & formal(found) ) { return false; } + //!< retrieves an entire "section", if supported by the derived object. + /*!< the symbol table "found" gets the entries from the "section". + see symbol_table.h for more details about string_tables. true is + returned if the section existed and its contents were put in "found". */ + + virtual bool put_section(const basis::astring & formal(section), + const structures::string_table & formal(to_store) ) { return false; } + //!< stores an entire "section" from the table in "to_store", if supported. + /*!< if any entries already exist in the "section", then they are + eliminated before the new entries are stored. true is returned if the + write was successful. */ + +private: + treatment_of_defaults _behavior; //!< records the treatment for defaults. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/ini_configurator.cpp b/nucleus/library/configuration/ini_configurator.cpp new file mode 100644 index 00000000..c65bdcca --- /dev/null +++ b/nucleus/library/configuration/ini_configurator.cpp @@ -0,0 +1,357 @@ +/*****************************************************************************\ +* * +* Name : ini_configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ini_configurator.h" +#include "application_configuration.h" +#include "variable_tokenizer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +using namespace basis; +using namespace filesystem; +using namespace structures; + +namespace configuration { + +//#define DEBUG_INI_CONFIGURATOR + // uncomment for noisy version. + +const int MAXIMUM_LINE_INI_CONFIG = 16384; + +// a default we hope never to see in an ini file. +SAFE_STATIC_CONST(astring, ini_configurator::ini_str_fake_default, ("NoTomatoesNorPotatoesNorQuayle")) + +ini_configurator::ini_configurator(const astring &ini_filename, + treatment_of_defaults behavior, file_location_default where) +: configurator(behavior), + _ini_name(new filename), +#ifdef __UNIX__ + _parser(new ini_parser("", behavior)), +#endif + _where(where), + _add_spaces(false) +{ + name(ini_filename); // set name properly. +} + +ini_configurator::~ini_configurator() +{ + WHACK(_ini_name); +#ifdef __UNIX__ + WHACK(_parser); +#endif +} + +astring ini_configurator::name() const { return _ini_name->raw(); } + +void ini_configurator::refresh() +{ +#ifdef __UNIX__ + write_ini_file(); + WHACK(_parser); + _parser = new ini_parser("", behavior()); +#endif +} + +void ini_configurator::name(const astring &name) +{ + *_ini_name = name; + + bool use_appdir = true; + // true if we should put files where programs start for those filenames + // that don't include a directory name. + if (_where == OS_DIRECTORY) use_appdir = false; + if (_where == ALL_USERS_DIRECTORY) use_appdir = false; +#ifndef __WIN32__ + use_appdir = true; +#endif + // we must create the filename if they specified no directory at all. + if (!_ini_name->had_directory()) { + if (use_appdir) { + // this is needed in case there is an ini right with the file; our + // standard is to check there first. + *_ini_name = filename(application_configuration::application_directory(), + _ini_name->basename()); + } else if (!use_appdir && (_where == ALL_USERS_DIRECTORY) ) { + // when the location default is all users, we get that from the + // environment. for the OS dir choice, we leave out the path entirely. + directory::make_directory(environment::get("ALLUSERSPROFILE") + + "/" + application_configuration::software_product_name()); + *_ini_name = filename(environment::get("ALLUSERSPROFILE") + + "/" + application_configuration::software_product_name(), + _ini_name->basename()); + } + } +#ifdef __UNIX__ + // read in the file's contents. + read_ini_file(); +#endif +} + +void ini_configurator::sections(string_array &list) +{ + list = string_array(); + // open our ini file directly as a file. + byte_filer section8(*_ini_name, "rb"); + if (!section8.good()) return; // not a healthy file. + astring line_found; + // iterate through the lines of the ini file and see if we can't find a + // bunch of section names. + while (section8.read(line_found, MAXIMUM_LINE_INI_CONFIG) > 0) { + // is the line in the format "^[ \t]*\[\([^\]]+\)\].*$" ? + // if it is in that format, we add the matched \1 into our list. + line_found.strip_white_spaces(); + if (line_found[0] != '[') continue; // no opening bracket. skip line. + line_found.zap(0, 0); // toss opening bracket. + int close_brack_indy = line_found.find(']'); + if (negative(close_brack_indy)) continue; // no closing bracket. + line_found.zap(close_brack_indy, line_found.end()); + list += line_found; + } +} + +//hmmm: refactor section_exists to use the sections call, if it's faser? +bool ini_configurator::section_exists(const astring §ion) +{ +#ifdef __WIN32__ + string_table infos; + // heavy-weight call here... + return get_section(section, infos); +#else + return _parser->section_exists(section); +#endif +} + +#ifdef __UNIX__ +void ini_configurator::read_ini_file() +{ +#ifdef DEBUG_INI_CONFIGURATOR + FUNCDEF("read_ini_file"); +#endif + _parser->reset(""); // clear out our current contents. + byte_filer ini_file; + bool open_ret = ini_file.open(*_ini_name, "rb"); // simple reading. +#ifdef DEBUG_INI_CONFIGURATOR + if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name); + if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name); +#endif + if (!open_ret || !ini_file.good()) { + return; // failure. + } + int file_size = ini_file.length(); // get the file length. + // read the file. + astring contents(' ', file_size + 3); + int bytes_read = ini_file.read((abyte *)contents.observe(), file_size); + contents.zap(bytes_read + 1, contents.end()); + _parser->reset(contents); +} + +void ini_configurator::write_ini_file() +{ +#ifdef DEBUG_INI_CONFIGURATOR + FUNCDEF("write_ini_file"); +#endif + +//hmmm: just set dirty flag and use that for deciding whether to write. +//hmmm: future version, have a thread scheduled to write. + + // open filer with new mode for cleaning. + byte_filer ini_file; + bool open_ret = ini_file.open(*_ini_name, "wb"); + // open the file for binary read/write and drop previous contents. +#ifdef DEBUG_INI_CONFIGURATOR + if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name); + if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name); +#endif + if (!open_ret || !ini_file.good()) return; // failure. + + // output table's contents to text. + astring text; + _parser->restate(text, _add_spaces); + ini_file.write((abyte *)text.observe(), text.length()); +} +#endif //UNIX + +bool ini_configurator::delete_section(const astring §ion) +{ +#ifdef __WIN32__ + return put_profile_string(section, "", ""); +#else + // zap the section. + bool to_return = _parser->delete_section(section); + // schedule the file to write. + write_ini_file(); + return to_return; +#endif +} + +bool ini_configurator::delete_entry(const astring §ion, const astring &ent) +{ +#ifdef __WIN32__ + return put_profile_string(section, ent, ""); +#else + // zap the entry. + bool to_return = _parser->delete_entry(section, ent); + // schedule the file to write. + write_ini_file(); + return to_return; +#endif +} + +bool ini_configurator::put(const astring §ion, const astring &entry, + const astring &to_store) +{ +/// FUNCDEF("put"); + if (!to_store.length()) return delete_entry(section, entry); + else if (!entry.length()) return delete_section(section); + else if (!section.length()) return false; +#ifdef __WIN32__ + return put_profile_string(section, entry, to_store); +#else + // write the entry. + bool to_return = _parser->put(section, entry, to_store); + // schedule file write. + write_ini_file(); + return to_return; +#endif +} + +bool ini_configurator::get(const astring §ion, const astring &entry, + astring &found) +{ +#ifndef __WIN32__ + return _parser->get(section, entry, found); +#else + flexichar temp_buffer[MAXIMUM_LINE_INI_CONFIG]; + temp_buffer[0] = 0; + get_profile_string(section, entry, ini_str_fake_default(), + temp_buffer, MAXIMUM_LINE_INI_CONFIG - 1); + found = from_unicode_temp(temp_buffer); + return !(ini_str_fake_default() == found); +#endif +} + +bool ini_configurator::get_section(const astring §ion, string_table &info) +{ +/// FUNCDEF("get_section"); +#ifndef __WIN32__ + return _parser->get_section(section, info); +#else + info.reset(); + const int buffer_size = 200000; + + flexichar low_buff[buffer_size + 3]; + int read_len = GetPrivateProfileSection(to_unicode_temp(section.observe()), + low_buff, buffer_size - 1, to_unicode_temp(name())); + if (!read_len) return false; // assume the API means there was no section. + + low_buff[read_len] = '\1'; // signal beyond the end of the stuff. + low_buff[read_len + 1] = '\0'; // make sure we're still zero terminated. + + bool last_was_nil = false; + // this loop replaces all the embedded nils with separators to allow the + // variable_tokenizer to retrieve all the strings from the section. + for (int i = 0; i < read_len; i++) { + if (!low_buff[i] && last_was_nil) { + // termination condition; we got two nils in a row. + // this is just paranoia; the length should tell us. + break; + } else if (!low_buff[i]) { + low_buff[i] = '\1'; // replace with a separator. + last_was_nil = true; + } else last_was_nil = false; // reset the nil flag. + } + + // now convert to a simple astring. + astring buff = from_unicode_temp(low_buff); + int length = buff.length(); + buff.shrink(); + variable_tokenizer parser("\1", "="); + parser.parse(buff); + info = parser.table(); + return true; +#endif +} + +bool ini_configurator::put_section(const astring §ion, + const string_table &info) +{ +#ifdef __WIN32__ + variable_tokenizer parser("\1", "="); + parser.table() = info; + astring flat = parser.text_form(); + flat += "\1\1"; // add terminating guard. + int len = flat.length(); + for (int i = 0; i < len; i++) { + if (flat[i] == '\1') { + flat[i] = '\0'; + if (flat[i+1] == ' ') { + // if the space character is next, shift it before the nil to avoid + // keys with a preceding space. + flat[i] = ' '; + flat[i + 1] = '\0'; + } + } + } + return WritePrivateProfileSection(to_unicode_temp(section), + to_unicode_temp(flat), to_unicode_temp(name())); +#else + // write the section. + bool to_return = _parser->put_section(section, info); + // schedule file write. + write_ini_file(); + return to_return; +#endif +} + +#ifdef __WIN32__ +bool ini_configurator::put_profile_string(const astring §ion, + const astring &entry, const astring &to_store) +{ + return bool(WritePrivateProfileString(to_unicode_temp(section), + entry.length() ? (flexichar *)to_unicode_temp(entry) : NIL, + to_store.length() ? (flexichar *)to_unicode_temp(to_store) : NIL, + to_unicode_temp(name()))); +} + +void ini_configurator::get_profile_string(const astring §ion, + const astring &entry, const astring &default_value, + flexichar *return_buffer, int buffer_size) +{ + GetPrivateProfileString(section.length() ? + (flexichar *)to_unicode_temp(section) : NIL, + entry.length() ? (flexichar *)to_unicode_temp(entry) : NIL, + to_unicode_temp(default_value), + return_buffer, buffer_size, to_unicode_temp(name())); +} +#endif + +} //namespace. + + diff --git a/nucleus/library/configuration/ini_configurator.h b/nucleus/library/configuration/ini_configurator.h new file mode 100644 index 00000000..ad99906d --- /dev/null +++ b/nucleus/library/configuration/ini_configurator.h @@ -0,0 +1,145 @@ +#ifndef INI_CONFIGURATOR_CLASS +#define INI_CONFIGURATOR_CLASS + +/*****************************************************************************\ +* * +* Name : ini_configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" +#ifndef __WIN32__ + #include "ini_parser.h" + #include +#endif + +#include +#include +#include + +namespace configuration { + +//! Supports a configurator-based interface on text initialization files. + +class ini_configurator : public configurator +{ +public: + //! chooses where the ini file is located if no path to it is provided. + /*! the ini file being manipulated will be stored in either the same + directory as the program being executed (APPLICATION_DIRECTORY) or in the + directory where the operating system resides (OS_DIRECTORY). however, the + OS_DIRECTORY choice only really makes sense on windows. if the flag is + instead ALL_USERS_DIRECTORY, then the directory pointed at by the + $ALLUSERSPROFILE variable will be used on windows; otherwise, the default + is the same as for APPLICATION_DIRECTORY. */ + enum file_location_default { + APPLICATION_DIRECTORY, //!< config files live with application. + OS_DIRECTORY, //!< config files live in operating system directory. + ALL_USERS_DIRECTORY //!< config files live in the "all users" account. + }; + + ini_configurator(const basis::astring &ini_filename, + treatment_of_defaults behavior = RETURN_ONLY, + file_location_default where = ALL_USERS_DIRECTORY); + //!< creates an ini_configurator that stores entries into "ini_filename". + /*!< the ini config will have the "behavior" specified for how to handle + missing items. "where" dictates the file's location if no path is + specified as part of the "ini_filename". */ + + virtual ~ini_configurator(); + + DEFINE_CLASS_NAME("ini_configurator"); + + void refresh(); + //!< useful mainly on unix/linux, where the file is parsed and held in memory. + +//hmmm: where are: +// save_to_file() +// is_modified() +//? + + basis::astring name() const; + //!< observes the name of the file used for ini entries. + void name(const basis::astring &name); + //!< modifies the name of the file used for ini entries. + + virtual bool get(const basis::astring §ion, const basis::astring &entry, + basis::astring &found); + //!< implements the configurator retrieval function. + /*!< this returns true if the entry was present and stores it in + "found". */ + + virtual void sections(structures::string_array &list); + //!< retrieves the section names into "list". + + virtual bool section_exists(const basis::astring §ion); + //!< returns true if the "section" was found in the file. + /*!< NOTE: for an INI file, this is quite a heavy-weight call. */ + + virtual bool put(const basis::astring §ion, const basis::astring &entry, + const basis::astring &to_store); + //!< implements the configurator storage function. + + virtual bool delete_section(const basis::astring §ion); + //!< removes the entire "section" specified. + + virtual bool delete_entry(const basis::astring §ion, const basis::astring &entry); + //!< removes the entry specified by the "section" and "entry" name. + + virtual bool get_section(const basis::astring §ion, structures::string_table &info); + //!< reads the entire "section" into a table called "info". + /*!< on win95, this will fail if the section's data exceeds 32K. */ + + virtual bool put_section(const basis::astring §ion, const structures::string_table &info); + //!< writes a table called "info" into the "section" in the INI file. + /*!< any existing data for that section is wiped out. on win95, this will + fail if the section's data exceeds 32K. */ + + // dictates whether the stored entries will have spaces between '=' + // and the key name and value. only applicable on linux. + bool add_spaces() const { return _add_spaces; } + void add_spaces(bool add_them) { _add_spaces = add_them; } + +private: + filesystem::filename *_ini_name; //!< the file we're manipulating. +#ifdef __UNIX__ + ini_parser *_parser; //!< used for real storage and parsing. +#endif + file_location_default _where; //!< where to find and store the file. + bool _add_spaces; //!< tracks whether we're adding spaces around equals. + +#ifdef __WIN32__ + bool put_profile_string(const basis::astring §ion, const basis::astring &entry, + const basis::astring &to_store); + //!< encapsulates windows' ini storage method. + void get_profile_string(const basis::astring §ion, const basis::astring &entry, + const basis::astring &default_value, basis::flexichar *return_buffer, + int buffer_size); + //!< encapsulates windows' ini retrieval method. +#endif +#ifdef __UNIX__ + void read_ini_file(); + //!< reads the INI file's contents into memory. + void write_ini_file(); + //!< store the current contents into the INI file. +#endif + + // not to be called. + ini_configurator(const ini_configurator &); + ini_configurator &operator =(const ini_configurator &); + + static const basis::astring &ini_str_fake_default(); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/ini_parser.cpp b/nucleus/library/configuration/ini_parser.cpp new file mode 100644 index 00000000..bb881c80 --- /dev/null +++ b/nucleus/library/configuration/ini_parser.cpp @@ -0,0 +1,237 @@ +/*****************************************************************************\ +* * +* Name : ini_parser * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ini_parser.h" +#include "table_configurator.h" +#include "variable_tokenizer.h" + +#include +#include +#include +#include +#include +#include + +//#define DEBUG_INI_PARSER + // uncomment for noisy version. + +#undef LOG +#ifdef DEBUG_INI_PARSER + #define LOG(to_print) printf("%s\n", astring(to_print).s()) +#else + #define LOG(a) {} +#endif + +////////////// + +using namespace basis; +using namespace structures; +using namespace textual; +//using namespace ; + +namespace configuration { + +//algorithm: +// gather section until next section definition or end of file. +// parse the section with variable_tokenizer. +// eat that out of the string. +// repeat. + +ini_parser::ini_parser(const astring &to_parse, treatment_of_defaults behavior) +: table_configurator(behavior), + _well_formed(false), + _preface(new astring) +{ + reset(to_parse); +} + +ini_parser::~ini_parser() +{ + WHACK(_preface); +} + +void ini_parser::chow_through_eol(astring &to_chow) +{ + while (to_chow.length()) { + if (parser_bits::is_eol(to_chow[0])) { + // zap all carriage return type chars now that we found one. + while (to_chow.length() && parser_bits::is_eol(to_chow[0])) { + *_preface += to_chow[0]; + to_chow.zap(0, 0); + } + return; // mission accomplished. + } + *_preface += to_chow[0]; + to_chow.zap(0, 0); + } +} + +/* +//this is a super expensive operation... +// it would be better to have the parser be a bit more intelligent. +void strip_blank_lines(astring &to_strip) +{ + bool last_was_ret = false; + for (int i = 0; i < to_strip.length(); i++) { + if (parser_bits::is_eol(to_strip[i])) { + if (last_was_ret) { + // two in a row; now that's bogus. + to_strip.zap(i, i); + i--; // skip back. + continue; + } + last_was_ret = true; + to_strip[i] = '\n'; // make sure we know which type to look for. + } else { + if (last_was_ret && parser_bits::white_space(to_strip[i])) { + // well, the last was a return but this is white space. that's also + // quite bogus. + to_strip.zap(i, i); + i--; // skip back. + continue; + } + last_was_ret = false; + } + } +} +*/ + +void ini_parser::reset(const astring &to_parse) +{ + _well_formed = false; + table_configurator::reset(); // clean out existing contents. + _preface->reset(); // set the preface string back to nothing. + add(to_parse); +} + +void ini_parser::add(const astring &to_parse) +{ + astring parsing = to_parse; +// strip_blank_lines(parsing); + _preface->reset(); // set the preface string back to nothing. + while (parsing.length()) { + astring section_name; + bool found_sect = parse_section(parsing, section_name); + if (!found_sect) { + // the line is not a section name. toss it. + chow_through_eol(parsing); + continue; // try to find another section name. + } + // we got a section. yee hah. + int next_sect = 0; + for (next_sect = 0; next_sect < parsing.length(); next_sect++) { +// LOG(astring("[") + astring(parsing[next_sect], 1) + "]"); + if (parser_bits::is_eol(parsing[next_sect])) { + // we found the requisite return; let's see if a section beginning + // is just after it. we know nothing else should be, since we stripped + // out the blank lines and blanks after CRs. + if (parsing[next_sect + 1] == '[') { + // aha, found the bracket that should be a section start. + break; // done seeking next section beginning. + } + } + } + // skip back one if we hit the end of the string. + if (next_sect >= parsing.length()) next_sect--; + // now grab what should be all values within a section. + LOG(a_sprintf("bounds are %d to %d, string len is %d.", 0, next_sect, + parsing.length())); + astring sect_parsing = parsing.substring(0, next_sect); + LOG(astring("going to parse: >>") + sect_parsing + "<<"); + parsing.zap(0, next_sect); + variable_tokenizer section_reader("\n", "="); + section_reader.set_comment_chars(";#"); + section_reader.parse(sect_parsing); + LOG(astring("read: ") + section_reader.text_form()); + merge_section(section_name, section_reader.table()); + } + _well_formed = true; +} + +void ini_parser::merge_section(const astring §ion_name, + const string_table &to_merge) +{ + if (!section_exists(section_name)) { + // didn't exist yet, so just plunk it in. + put_section(section_name, to_merge); + return; + } + + // since the section exists, we just write the individual entries from the + // new section. they'll stamp out any old values. + for (int i = 0; i < to_merge.symbols(); i++) + put(section_name, to_merge.name(i), to_merge[i]); +} + +bool ini_parser::parse_section(astring &to_parse, astring §ion_name) +{ + section_name = ""; // reset the section. + + // we have a simple state machine here... + enum states { + SEEKING_OPENING_BRACKET, // looking for the first bracket. + EATING_SECTION_NAME // got a bracket, now getting section name. + }; + states state = SEEKING_OPENING_BRACKET; + + // zip through the string trying to find a valid section name. + for (int i = 0; i < to_parse.length(); i++) { + char curr = to_parse[i]; + LOG(astring("<") + astring(curr, 1) + ">"); + switch (state) { + case SEEKING_OPENING_BRACKET: + // we're looking for the first bracket now... + if (parser_bits::white_space(curr)) continue; // ignore white space. + if (curr != '[') return false; // argh, bad characters before bracket. + state = EATING_SECTION_NAME; // found the bracket. + break; + case EATING_SECTION_NAME: + // we're adding to the section name now... + if (curr == ']') { + // that's the end of the section name. + to_parse.zap(0, i); // remove what we saw. +//should we take out to end of line also? +//eventually up to eol could be kept as a comment? + return true; + } + section_name += curr; // add a character to the name. + break; + default: + //LOG("got to unknown case in section parser!"); + return false; + } + } + // if we got to here, the section was badly formed... the whole string was + // parsed through but no conclusion was reached. + return false; +} + +bool ini_parser::restate(astring &new_ini, bool add_spaces) +{ + new_ini = *_preface; // give it the initial text back again. + string_array sects; + sections(sects); + for (int i = 0; i < sects.length(); i++) { + new_ini += astring("[") + sects[i] + "]" + parser_bits::platform_eol_to_chars(); + string_table tab; + if (!get_section(sects[i], tab)) continue; // serious error. + tab.add_spaces(add_spaces); + new_ini += tab.text_form(); + } + return true; +} + +} //namespace. + + diff --git a/nucleus/library/configuration/ini_parser.h b/nucleus/library/configuration/ini_parser.h new file mode 100644 index 00000000..fb061dab --- /dev/null +++ b/nucleus/library/configuration/ini_parser.h @@ -0,0 +1,124 @@ +#ifndef INI_PARSER_CLASS +#define INI_PARSER_CLASS + +/*****************************************************************************\ +* * +* Name : ini_parser * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "table_configurator.h" + +namespace configuration { + +//! Parses strings in the fairly well-known INI file format. +/*! + Description of INI file format: + + The format expected in this parser for initialization files allows for + three types of entries. These are section headers, comments and value + definitions. + Section headers define the start of a list of value definitions. A + section header is a name in brackets, like [startup]. + A comment is a string of text that will be ignored. Comments are preceded + by an octothorpe ('#') or a semicolon (';'). The parser will keep comments + roughly in the same places they were found in the string that was parsed. + A comment is allowed to follow a section header on the same line. + Value definitions are a pair of strings separated by an equality operator + ('='). The left side of the value definition is referred to here as the + variable's name while the right side is referred to as the variable's value. + Note that any line which is not a comment or a section header is considered + implicitly to be a value definition, even if it does not contain an equals + operator. This is required for parsing certain INI files that don't follow + the established format. Such lines will have an empty string for their + value. + White space (either tab characters or space characters) are allowed before + and after any of these constructs. Spaces may also exist before and after + the equals operator of a value definition, but once the value starts, any + white space is considered part of the value. Trailing white space is not + considered part of a variable name, but white space between the characters + before the equals operator is signficant. Any number of carriage returns + can separate the lines of the INI file. + + Here is an example of a valid INI file: + @code + # Initialization file for Frootnik Corp. + + [common] ; all of our programs use these. + magnification=1 + + text_color=puce + font=atavata + ;;; see font/color document in readme.txt. + + [sourceburger] ; sourceburger application specific settings + + crashnow = 0 + crashlater = 1 + get clue=0 + windowtitle = Source Burger 5000 + + danger will robinson + @endcode +*/ + +class ini_parser : public table_configurator +{ +public: + ini_parser(const basis::astring &to_parse, + treatment_of_defaults behavior = RETURN_ONLY); + //!< constructs an ini_parser by parsing entries out of "to_parse". + /*!< after construction, the success of parsing can be checked using + well_formed(). */ + + ~ini_parser(); + + void reset(const basis::astring &to_parse); + //!< drops any existing information and processes the string "to_parse". + + void add(const basis::astring &to_parse); + //!< merges items parsed from "to_parse" into the current set. + /*!< processes the string "to_parse" as in the reset() method but adds + any new sections found to our configuration. if sections are found with + the same names, then the values found in "to_parse" override the ones + already listed. */ + + bool well_formed() const { return _well_formed; } + //!< returns true if the ini file's contents were in the format expected. + + bool restate(basis::astring &new_ini, bool add_spaces = false); + //!< stores a cleaned version of the internal state into "new_ini". + /*!< if "add_spaces" is true, then the entries will be in the form of + 'x = y' rather than 'x=y'. */ + + void merge_section(const basis::astring §ion_name, const structures::string_table &to_merge); + //!< merges the table "to_merge" into the "section_name". + /*!< any new values from "to_merge" that are not found in the section with + "section_name" in "this" object are added and any existing values will be + replaced. */ + +private: + bool _well_formed; //!< true if the ini file had a valid format. + basis::astring *_preface; //!< information that comes before the first section. + + void chow_through_eol(basis::astring &to_chow); + //!< eats up to an EOL character but adds the text to our preface string. + + bool parse_section(basis::astring &to_parse, basis::astring §ion_name); + //!< looks for a section name in the string "to_parse". + /*!< true is returned on success; success means that a "section_name" was + found and that "to_parse" has been destructively eaten to remove it. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/ini_roller.cpp b/nucleus/library/configuration/ini_roller.cpp new file mode 100644 index 00000000..36110081 --- /dev/null +++ b/nucleus/library/configuration/ini_roller.cpp @@ -0,0 +1,110 @@ +/*****************************************************************************\ +* * +* Name : ini_roller * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ini_roller.h" + +#include +#include +#include +///#include + +using namespace basis; +using namespace structures; + +namespace configuration { + +//#define DEBUG_ID_GRANTING + // uncomment if you want verbose granting of unique ids. + +const int ID_FACTOR = 28; + // this many ids are grabbed at once for eventual issuance. + +ini_roller::ini_roller(configurator &config, const astring §ion, + const astring &entry, int min, int max) +: _ini(config), + _ids(new int_roller(min, max)), + _section(new astring(section)), + _entry(new astring(entry)), + _lock(new mutex) +{ + int current = _ini.load(section, entry, min); + _ids->set_current(current); + // make our first requisition of ids. we start here rather than playing + // games with the next_id function. + _ini.store(section, entry, _ids->current() + ID_FACTOR); +} + +ini_roller::~ini_roller() +{ + // force the id to be past what we've allocated, but not too far past. + _ini.store(*_section, *_entry, _ids->current() + 1); + WHACK(_ids); + WHACK(_section); + WHACK(_entry); + WHACK(_lock); +} + +int ini_roller::current_id() const +{ + auto_synchronizer l(*_lock); + return _ids->current(); +} + +int ini_roller::next_id() +{ +#ifdef DEBUG_ID_GRANTING + FUNCDEF("next_id"); +#endif + auto_synchronizer l(*_lock); + int to_return = _ids->current(); + + // this uses a relaxed id issuance policy; the id that's in the INI + // file is only updated when we run out of the range that we allocate for it. + // the roller's current value is used whenever issuing an id, but next_id() + // is always called before that id is actually issued. + + if ( (_ids->current() < _ids->maximum() - 2) + && (_ids->current() % ID_FACTOR) ) { + // no id range grabbing needed yet and no rollover. + _ids->next_id(); +#ifdef DEBUG_ID_GRANTING + LOG(astring(astring::SPRINTF, "standard id issue: %d.", to_return)); +#endif + return to_return; + } + + // now we need to allocate a new range of ids... and store in ini. + int new_range = to_return + ID_FACTOR; +#ifdef DEBUG_ID_GRANTING + LOG(astring(astring::SPRINTF, "finding next range, new start in ini " + "is: %d.", new_range)); +#endif + // if the id wraps around, reset it. + if ( (new_range < 0) || (new_range >= _ids->maximum()) ) + new_range = ID_FACTOR; +#ifdef DEBUG_ID_GRANTING + LOG(astring(astring::SPRINTF, "after check, new ini id is: %d.", + new_range)); +#endif + _ini.store(*_section, *_entry, new_range); + // set the next stored id to the block above where we're using. + _ids->next_id(); // jump to the next one in the range. +#ifdef DEBUG_ID_GRANTING + LOG(astring(astring::SPRINTF, "after store, id is: %d.", to_return)); +#endif + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/configuration/ini_roller.h b/nucleus/library/configuration/ini_roller.h new file mode 100644 index 00000000..f41469ea --- /dev/null +++ b/nucleus/library/configuration/ini_roller.h @@ -0,0 +1,73 @@ +#ifndef INI_ROLLER_CLASS +#define INI_ROLLER_CLASS + +/*****************************************************************************\ +* * +* Name : ini_roller * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" + +#include +#include +#include + +namespace configuration { + +//! Implements an id generator that interacts with a configuration file. +/*! + This provides an int_roller (which provides rolling ids given a range to + issue them from) that is stored in a configurator. The instantiated + object is the real source of the ids, but the configurator file is + periodically updated to reflect the current id state. If a program + is restarted later, then it will start using ids that it had not already + issued in its last run, as long as the configurator is a persistent object. + Note that the range of ids had better be quite large; otherwise the program + could still have live entries under an id that is about to be reissued + due to wrap-around. +*/ + +class ini_roller : public virtual basis::root_object +{ +public: + ini_roller(configurator &config, const basis::astring §ion, + const basis::astring &entry, int min, int max); + //!< creates a roller that updates "config" with the current id value. + /*!< the updates are not continuous, but are done periodically to avoid + constantly writing to the "config". the "section" and "entry" dictate + where the entry is saved in the "config". the "min" and "max" provide the + range of the id, where it will start at "min", increment by one until it + reaches at most "max", and then it will start back at "min" again. note + that "config" must exist for the duration of the ini_roller. */ + + virtual ~ini_roller(); + + DEFINE_CLASS_NAME("ini_roller"); + + int next_id(); + //!< returns the next number to be issued. + + int current_id() const; + //!< returns the current id; this is the one that was last issued. + +private: + configurator &_ini; //!< provides access to the ini file. + structures::int_roller *_ids; //!< the current id number is managed here. + basis::astring *_section; //!< remembers the right section for our id entry. + basis::astring *_entry; //!< remembers the entry name. + basis::mutex *_lock; //!< keeps us thread safe. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/makefile b/nucleus/library/configuration/makefile new file mode 100644 index 00000000..1ac89e79 --- /dev/null +++ b/nucleus/library/configuration/makefile @@ -0,0 +1,11 @@ +include cpp/variables.def + +PROJECT = configuration +TYPE = library +SOURCE = application_configuration.cpp config_watcher.cpp configurator.cpp configlet.cpp \ + configuration_list.cpp ini_configurator.cpp ini_parser.cpp ini_roller.cpp \ + section_manager.cpp system_values.cpp table_configurator.cpp variable_tokenizer.cpp +TARGETS = configuration.lib + +include cpp/rules.def + diff --git a/nucleus/library/configuration/section_manager.cpp b/nucleus/library/configuration/section_manager.cpp new file mode 100644 index 00000000..eabf5d76 --- /dev/null +++ b/nucleus/library/configuration/section_manager.cpp @@ -0,0 +1,113 @@ + + + +/*****************************************************************************\ +* * +* Name : section_manager * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "section_manager.h" + +#include +#include +#include +#include + +using namespace basis; +using namespace configuration; +using namespace structures; + +namespace configuration { + +section_manager::section_manager(configurator &config, + const astring &toc_title, const astring &header_prefix) +: _config(config), + _toc_title(new astring(toc_title)), + _section_prefix(new astring(header_prefix)) +{ +} + +section_manager::~section_manager() +{ + WHACK(_toc_title); + WHACK(_section_prefix); +} + +bool section_manager::get_toc(string_table &toc) +{ return _config.get_section(*_toc_title, toc); } + +bool section_manager::get_section_names(string_array §ions) +{ + sections.reset(); // clean up the array they gave us. + string_table toc; + if (!get_toc(toc)) return false; + for (int i = 0; i < toc.symbols(); i++) sections += toc.name(i); + return true; +} + +bool section_manager::section_exists(const astring §ion_name) +{ + if (!section_name) return false; // empty names are invalid. + return _config.load(*_toc_title, section_name, "").t(); +} + +astring section_manager::make_section_heading(const astring §ion) +{ return *_section_prefix + section; } + +bool section_manager::add_section(const astring §ion_name, + const string_table &to_add) +{ + if (!section_name) return false; // empty names are invalid. + if (section_exists(section_name)) return false; + // write the name into the table of contents. + astring section_value = "t"; // place-holder for section entries. + if (!to_add.symbols()) + section_value = ""; // nothing to write; delete the section entry. + if (!_config.put(*_toc_title, section_name, section_value)) + return false; + // create the appropriate header for the new section. + astring header = make_section_heading(section_name); + // if there aren't any elements, the put_section should just wipe out + // the entire section. + return _config.put_section(header, to_add); +} + +bool section_manager::replace_section(const astring §ion_name, + const string_table &replacement) +{ + if (!section_name) return false; // empty names are invalid. + if (!section_exists(section_name)) return false; + if (!zap_section(section_name)) return false; + if (!replacement.symbols()) return true; // nothing to write. + return add_section(section_name, replacement); +} + +bool section_manager::zap_section(const astring §ion_name) +{ + if (!section_name) return false; // empty names are invalid. + astring header = make_section_heading(section_name); + if (!_config.delete_section(header)) return false; + return _config.delete_entry(*_toc_title, section_name); +} + +bool section_manager::find_section(const astring §ion_name, + string_table &found) +{ + if (!section_name) return false; // empty names are invalid. + if (!section_exists(section_name)) return false; + astring header = make_section_heading(section_name); + return _config.get_section(header, found); +} + +} //namespace. + + diff --git a/nucleus/library/configuration/section_manager.h b/nucleus/library/configuration/section_manager.h new file mode 100644 index 00000000..dea180c4 --- /dev/null +++ b/nucleus/library/configuration/section_manager.h @@ -0,0 +1,111 @@ +#ifndef SECTION_MANAGER_CLASS +#define SECTION_MANAGER_CLASS + +/*****************************************************************************\ +* * +* Name : section_manager * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" + +#include + +namespace configuration { + +//! Tracks a collection of related configurations in a configurator. +/*! + If there is a set of items that need to be stored in a configurator, where + each item has its own configuration section, then this object can help out. + It manages a collection of uniquely named sections in a configurator object + and provides a table of contents (TOC) feature for the names of the sections. + Each item lives in its own distinct section but the whole set can be + operated on as one entity. +*/ + +class section_manager : public virtual basis::nameable +{ +public: + section_manager(configurator &config, const basis::astring &toc_title, + const basis::astring &header_prefix); + //!< creates a section_manager that uses the "config" for storage. + /*!< the "toc_title" is the name of the section to be used for storing the + table of contents for the managed configuration items. the "header_prefix" + will be prepended to the names of each section to facilitate locating them. + for example, if the "toc_title" is "client channels" and the "header_prefix" + is "CliChan__", then the resulting configuration might look similar to the + following: @code + [client channels] + joe + ted + [CliChan__joe] + port=58 + [CliChan__ted] + address=13.8.92.4 + auth=primary + @endcode */ + + ~section_manager(); + + DEFINE_CLASS_NAME("section_manager"); + + bool section_exists(const basis::astring §ion_name); + //!< returns true if the section called "section_name" exists in the config. + + bool get_section_names(structures::string_array §ions); + //!< loads the "sections" array with all section names. + /*!< this comes from our table of contents. true is returned if there + were any names to load. */ + + bool add_section(const basis::astring §ion_name, const structures::string_table &to_add); + //!< stores a new section for "section_name" using the table "to_add". + /*!< this will fail if the section already exists. */ + + bool replace_section(const basis::astring §ion, const structures::string_table &replacement); + //!< replaces the contents of "section" with the "replacement" table. + /*!< this will fail if the section does not already exist. */ + + bool zap_section(const basis::astring §ion_name); + //!< removes the data for "section_name" from both the config and TOC. + /*!< this will fail if the section is not present. */ + + bool find_section(const basis::astring §ion_name, structures::string_table &found); + //!< loads the data from "section_name" into the table "found". + /*!< this fails if the section doesn't exist or if the section's contents + couldn't be detokenized into a table of name/value pairs. */ + + configurator &config() { return _config; } + //!< allows access to the configurator we operate on. + /*!< getting single items from the config will be signficantly faster + using it directly; the make_section_heading() method can be used to locate + the proper section. */ + + bool get_toc(structures::string_table &toc); + //!< reads the table of contents into "toc". + + basis::astring make_section_heading(const basis::astring §ion); + //!< provides the appropriate heading string for the "section" name. + /*!< this can be used to find entries using the config(). */ + +private: + configurator &_config; //!< the configuration object we interact with. + basis::astring *_toc_title; //!< the table of contents' section name. + basis::astring *_section_prefix; //!< prefix attached to the name of the section. + + section_manager(const section_manager &); //!< currently forbidden. + section_manager &operator =(const section_manager &); + //!< currently forbidden. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/system_values.cpp b/nucleus/library/configuration/system_values.cpp new file mode 100644 index 00000000..c82b515e --- /dev/null +++ b/nucleus/library/configuration/system_values.cpp @@ -0,0 +1,207 @@ +/*****************************************************************************\ +* * +* Name : system_values * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ini_configurator.h" +#include "system_values.h" + +#include +#include +#include +#include +#include + +using namespace algorithms; +using namespace basis; +using namespace structures; +using namespace textual; + +namespace configuration { + +const int MAX_VALUE_BITS = 8; // we provide 2^n slots in hash. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +////////////// + +class value_record +{ +public: + astring _name; // the name of the value. + astring _descrip; // the description of the value. + astring _location; // the file defining the value. + + value_record(const astring &name = astring::empty_string(), + const astring &description = astring::empty_string(), + const astring &location = astring::empty_string()) + : _name(name), _descrip(description), _location(location) {} +}; + +////////////// + +class system_values_lookup_list : public int_hash +{ +public: + system_values_lookup_list() : int_hash(MAX_VALUE_BITS) {} + + // finds the symbolic "name" in the table, which is not as efficient as + // lookin up integers. + value_record *text_find(const astring &name, int &value) { + // scoot across all of the ids. + const int_set &cids = ids(); + for (int i = 0; i < cids.elements(); i++) { + int current_id = cids[i]; + value_record *curr = find(current_id); + if (!curr) { +//serious error. + continue; + } + if (curr->_name == name) { + // this is a match to the name they were seeking. + value = current_id; + return curr; + } + } + return NIL; + } +}; + +////////////// + +system_values::system_values(const astring §ion_tag) +: _tag(new astring(section_tag)), + _list(new system_values_lookup_list), + _file(new astring(DEFAULT_MANIFEST)) +{ +// FUNCDEF("constructor"); + open_values(); +} + +system_values::~system_values() +{ + WHACK(_list); + WHACK(_tag); + WHACK(_file); +} + +const char *system_values::DEFAULT_MANIFEST = "manifest.txt"; + // this is the default manifest and it is expected to live right in + // the folder where the applications are. + +bool system_values::use_other_manifest(const astring &manifest_file) +{ + *_file = manifest_file; + return open_values(); +} + +const char *system_values::OUTCOME_VALUES() { return "DEFINE_OUTCOME"; } + +const char *system_values::FILTER_VALUES() { return "DEFINE_FILTER"; } + +const char *system_values::EVENT_VALUES() { return "DEFINE_EVENT"; } + +bool system_values::open_values() +{ +// FUNCDEF("open_values"); + ini_configurator ini(*_file, ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY); + + string_table full_section; + bool got_section = ini.get_section(*_tag, full_section); + if (!got_section) return false; // nothing there to look up. + for (int i = 0; i < full_section.symbols(); i++) { + + string_array items; + list_parsing::parse_csv_line(full_section.name(i), items); + if (items.length() < 4) { + continue; + } + + value_record *entry = new value_record(items[0], items[2], items[3]); + int value = items[1].convert(0); + _list->add(value, entry); + } + + return true; +} + +#define SV_EOL parser_bits::platform_eol_to_chars() + +//hmmm: it might be nice to have an alternate version sorted by name... + +astring system_values::text_form() const +{ + int_set cids = _list->ids(); + + if (!_tag->equal_to("DEFINE_OUTCOME")) { + // sort the list in identifier order. + shell_sort(cids.access(), cids.elements()); + } else { + // sort the list in reverse identifier order, since zero is first + // for outcomes and then they go negative. + shell_sort(cids.access(), cids.elements(), true); + } + + astring to_return("values for "); + to_return += *_tag; + to_return += SV_EOL; + for (int i = 0; i < cids.elements(); i++) { + int current_id = cids[i]; + value_record *curr = _list->find(current_id); + if (!curr) { +//serious error. + continue; + } + to_return += a_sprintf("%d: ", current_id); + to_return += curr->_name + " \"" + curr->_descrip + "\" from " + + curr->_location; + to_return += SV_EOL; + } + return to_return; +} + +bool system_values::lookup(int value, astring &symbolic_name, + astring &description, astring &file_location) +{ + value_record *found = _list->find(value); + if (!found) return false; + symbolic_name = found->_name; + description = found->_descrip; + file_location = found->_location; + return true; +} + +bool system_values::lookup(const astring &symbolic_name, int &value, + astring &description, astring &file_location) +{ + value_record *found = _list->text_find(symbolic_name, value); + if (!found) return false; + description = found->_descrip; + file_location = found->_location; + return true; +} + +int system_values::elements() const { return _list->ids().elements(); } + +bool system_values::get(int index, astring &symbolic_name, int &value, + astring &description, astring &file_location) +{ + bounds_return(index, 0, _list->ids().elements() - 1, false); // bad index. + value = _list->ids()[index]; + return lookup(value, symbolic_name, description, file_location); +} + +} //namespace. + + diff --git a/nucleus/library/configuration/system_values.h b/nucleus/library/configuration/system_values.h new file mode 100644 index 00000000..02c11b7f --- /dev/null +++ b/nucleus/library/configuration/system_values.h @@ -0,0 +1,117 @@ +#ifndef SYSTEM_VALUES_CLASS +#define SYSTEM_VALUES_CLASS + +/*****************************************************************************\ +* * +* Name : system_values * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +//#include + +namespace configuration { + +// forward. +class system_values_lookup_list; + +//! This class provides a way to look up generated values used in the code base. +/*! + The type of value here includes outcomes, events and filters so far. These + items are reported in the build manifest that is included with every release + of the compiled software. The system_values class provides a lookup + interface to the extensible system of unique identifiers which is mapped by + the manifest file. The manifest file is processed like an initialization + file to retrieve the descriptions and names of symbols when given their + numerical identifiers. +*/ + +class system_values : public virtual basis::root_object +{ +public: + system_values(const basis::astring §ion_tag); + //!< provides a lookup on values found in the section named "section_tag". + /*!< the "section_tag" indicates what kind of asset is being looked up in + the manifest. it is always assumed that the manifest file is the main + one defined here in DEFAULT_MANIFEST. it must be a file created by + the value_tagger during the indexing of all value assets defined in the + build. + the valid values for the section names so far are "DEFINE_ OUTCOME", + "DEFINE_ FILTER" and "DEFINE_ EVENT" (with no spaces), but it is better + to use the defined "VALUES" methods below (such as OUTCOME_VALUES()). + outcomes are used by functions to describe how the operation completed. + filters are used to enable different types of logging. events are sent + between information source and sink to indicate the occurrence of an + activity. */ + + virtual ~system_values(); + + DEFINE_CLASS_NAME("system_values"); + + // these provide symbolic versions of the section tag used in the + // constructor. these are preferable to using the string constants + // directly. + static const char *OUTCOME_VALUES(); + //!< values that define the outcomes of operations. + static const char *FILTER_VALUES(); + //!< values that define filters used in logging. + static const char *EVENT_VALUES(); + //!< values that define event objects used in the program. + + static const char *DEFAULT_MANIFEST; + //!< the default manifest file. + /*!< it is expected to live in the folder where the applications are. */ + + bool use_other_manifest(const basis::astring &manifest_file); + //!< supports using a different manifest file than the default. + /*!< the values will now come from the "manifest_file" instead of the + default manifest name shipped with the software release. */ + + virtual basis::astring text_form() const; + //!< shows all items in the table. + + bool lookup(int value, basis::astring &symbolic_name, basis::astring &description, + basis::astring &file_location); + //!< locates a "value" and finds its name, description and location. + /*!< this looks up one of the enumerated values defined for our type of + value. true is returned if the value is meaningful. the "symbolic_name" + is set to the item's name as used inside the source code (i.e., its enum + member's name), the "description" is set to the full textual description + of what that value means, and the "file_location" is set to the name of + the source file that defines the value. */ + + bool lookup(const basis::astring &symbolic_name, int &value, basis::astring &description, + basis::astring &file_location); + //!< similar to the above lookup, but seeks on the "symbolic_name". + /*!< this lookup tries to associate from the textual name of the value + in order to find the integer actual "value" of it. the textual + "description" and "file_location" for that item are also included. */ + + int elements() const; + //!< returns how many items are listed for the types of values specified. + + bool get(int index, basis::astring &symbolic_name, int &value, + basis::astring &description, basis::astring &file_location); + //!< accesses the "index"th item in the list. + +private: + basis::astring *_tag; //!< the name of our section. + system_values_lookup_list *_list; //!< the values and text that we found. + basis::astring *_file; //!< the manifest filename. + + bool open_values(); //!< retrieves the information from the file. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/table_configurator.cpp b/nucleus/library/configuration/table_configurator.cpp new file mode 100644 index 00000000..692ed0d0 --- /dev/null +++ b/nucleus/library/configuration/table_configurator.cpp @@ -0,0 +1,197 @@ +/*****************************************************************************\ +* * +* Name : table_configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "table_configurator.h" + +#include +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace configuration { + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +class table_o_string_tables : public symbol_table +{ +public: +}; + +////////////// + +table_configurator::table_configurator(treatment_of_defaults behavior) +: configurator(behavior), + _real_table(new table_o_string_tables) +{} + +table_configurator::table_configurator(const table_configurator &to_copy) +: configurator(to_copy.behavior()), + _real_table(new table_o_string_tables) +{ *this = to_copy; } + +table_configurator::~table_configurator() +{ + WHACK(_real_table); +} + +table_configurator &table_configurator::operator = + (const table_configurator &to_copy) +{ + if (this == &to_copy) return *this; + reset(); + string_array sects; + const_cast(to_copy).sections(sects); + for (int sectindy = 0; sectindy < sects.length(); sectindy++) { + // every entry in the current section gets added to our current config. + astring curr_section = sects[sectindy]; + string_table entries; + const_cast(to_copy).get_section(curr_section, entries); + put_section(curr_section, entries); + } + + return *this; +} + +void table_configurator::reset() { _real_table->reset(); } + +bool table_configurator::section_exists(const astring §ion) +{ return !!_real_table->find(section); } + +void table_configurator::sections(string_array &to_fill) +{ + to_fill.reset(); + for (int i = 0; i < _real_table->symbols(); i++) + to_fill += _real_table->name(i); +} + +bool table_configurator::delete_section(const astring §ion) +{ return _real_table->whack(section) == common::OKAY; } + +bool table_configurator::delete_entry(const astring §ion, + const astring &ent) +{ + string_table *sect = _real_table->find(section); + if (!sect) return false; + return sect->whack(ent) == common::OKAY; +} + +bool table_configurator::put(const astring §ion, + const astring &entry, const astring &to_store) +{ + if (!to_store.length()) return delete_entry(section, entry); + else if (!entry.length()) return delete_section(section); + string_table *sect = _real_table->find(section); + if (!sect) { + // none exists yet, so add one. + _real_table->add(section, string_table()); + sect = _real_table->find(section); + } + sect->add(entry, to_store); + return true; +} + +bool table_configurator::get(const astring §ion, + const astring &entry, astring &found) +{ + found = ""; + string_table *sect = _real_table->find(section); + if (!sect) return false; + astring *looked = sect->find(entry); + if (!looked) return false; + found = *looked; + return true; +} + +/* +scavenge? +bool is_comment(char to_check, const char *comment_list) +{ + int len = int(strlen(comment_list)); + for (int i = 0; i < len; i++) { + if (to_check == comment_list[i]) + return true; + } + return false; +} +*/ + +/* scavenge? +//hmmm: could we move the commented and clean_comments methods into +// parser bits? +// yes! we should move those; they are no longer used here! + +bool table_configurator::commented(const astring &to_check, + const char *comment_list) +{ + for (int i = 0; i < to_check.length(); i++) { + if (white_space(to_check[i])) + continue; // skip spaces. + if (is_comment(to_check[i], comment_list)) + return true; // started with a comment. + return false; // we had our chance for a comment, but that wasn't it. + } + return false; +} + +astring table_configurator::clean_comments(const astring &to_clean, + const char *comment_list) +{ + FUNCDEF("clean_comments"); +//LOG(astring("clean in with: ") + to_clean); + astring to_return(' ', to_clean.length()); // make a long enough string. + to_return.reset(); // keep allocated buffer size, but throw out contents. + for (int i = 0; i < to_clean.length(); i++) { + if (is_comment(to_clean[i], comment_list)) { + // here we go; the rest is commented out. + break; + } + to_return += to_clean[i]; + } +//LOG(astring("clean out with: ") + to_return); + return to_return; +} +*/ + +bool table_configurator::get_section(const astring §ion, + string_table &info) +{ +/// FUNCDEF("get_section"); + info.reset(); + string_table *sect = _real_table->find(section); + if (!sect) return false; + for (int i = 0; i < sect->symbols(); i++) + info.add(sect->name(i), (*sect)[i]); + return true; +} + +bool table_configurator::put_section(const astring §ion, + const string_table &info) +{ +/// FUNCDEF("put_section"); + string_table *sect = _real_table->find(section); + if (!sect) { + // none exists yet, so add one. + _real_table->add(section, string_table()); + sect = _real_table->find(section); + } + *sect = info; + return true; +} + +} //namespace. + diff --git a/nucleus/library/configuration/table_configurator.h b/nucleus/library/configuration/table_configurator.h new file mode 100644 index 00000000..4beaee00 --- /dev/null +++ b/nucleus/library/configuration/table_configurator.h @@ -0,0 +1,82 @@ +#ifndef TABLE_CONFIGURATOR_CLASS +#define TABLE_CONFIGURATOR_CLASS + +/*****************************************************************************\ +* * +* Name : table_configurator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configurator.h" + +#include + +namespace configuration { + +// forward. +class table_o_string_tables; + +//! Supports the configurator interface using a collection of string tables. + +class table_configurator : public virtual configurator +{ +public: + table_configurator(treatment_of_defaults behavior = AUTO_STORE); + //!< Constructor just needs to know what to do for missing items. + /*!< Creates a table_configurator that loads and stores entries into + the internal collection of tables. It will use the "behavior" regarding + missing entries when load() is invoked. */ + + table_configurator(const table_configurator &to_copy); + + virtual ~table_configurator(); + + table_configurator &operator =(const table_configurator &to_copy); + + DEFINE_CLASS_NAME("table_configurator"); + + virtual void sections(structures::string_array &list); + //!< retrieves the section names into "list". + + void reset(); // clears out all contents. + + virtual bool get(const basis::astring §ion, const basis::astring &entry, + basis::astring &found); + //!< implements the configurator retrieval function. + + virtual bool put(const basis::astring §ion, const basis::astring &entry, + const basis::astring &to_store); + //!< implements the configurator storage function. + + virtual bool section_exists(const basis::astring §ion); + //!< true if the "section" is presently in the table config. + + virtual bool delete_section(const basis::astring §ion); + //!< removes the entire "section" specified. + + virtual bool delete_entry(const basis::astring §ion, const basis::astring &entry); + //!< removes the entry specified by the "section" and "entry" name. + + virtual bool get_section(const basis::astring §ion, structures::string_table &info); + //!< reads the entire table held under "section" into a table called "info". + + virtual bool put_section(const basis::astring §ion, const structures::string_table &info); + //!< writes a table called "info" into the "section" held here. + +private: + table_o_string_tables *_real_table; + //!< the data structure we're actually operating on. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/configuration/variable_tokenizer.cpp b/nucleus/library/configuration/variable_tokenizer.cpp new file mode 100644 index 00000000..7a1d0fd5 --- /dev/null +++ b/nucleus/library/configuration/variable_tokenizer.cpp @@ -0,0 +1,403 @@ +// Name : variable_tokenizer +// Author : Chris Koeritz +/* +* Copyright (c) 1997-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "variable_tokenizer.h" + +#include +#include +#include +#include +#include +#include + +//#define DEBUG_VARIABLE_TOKENIZER + // uncomment for noisier run. + +const char *SPECIAL_VALUE = " "; + // special value stored for entries with assignment operators but no + // value contents. + +#undef LOG +#ifdef DEBUG_VARIABLE_TOKENIZER + #include + #define LOG(to_print) printf("%s\n", astring(to_print).s()); +#else + #define LOG(to_print) +#endif + +using namespace basis; +using namespace structures; +using namespace textual; + +namespace configuration { + +variable_tokenizer::variable_tokenizer(int max_bits) +: _implementation(new string_table(max_bits)), + _assignments(new astring("=")), + _separators(new astring(",")), + _quotes(new astring), + _nesting(false), + _comments(new astring), + _comment_number(1), + _add_spaces(false) +{} + +variable_tokenizer::variable_tokenizer(const astring &separator, const astring &assignment, + int max_bits) +: _implementation(new string_table(max_bits)), + _assignments(new astring(assignment)), + _separators(new astring(separator)), + _quotes(new astring), + _nesting(false), + _comments(new astring), + _comment_number(1), + _add_spaces(false) +{} + +variable_tokenizer::variable_tokenizer(const astring &separator, const astring &assignment, + const astring "es, bool nesting, int max_bits) +: _implementation(new string_table(max_bits)), + _assignments(new astring(assignment)), + _separators(new astring(separator)), + _quotes(new astring(quotes)), + _nesting(nesting), + _comments(new astring), + _comment_number(1), + _add_spaces(false) +{} + +variable_tokenizer::variable_tokenizer(const variable_tokenizer &to_copy) +: _implementation(new string_table), + _assignments(new astring), + _separators(new astring), + _quotes(new astring), + _nesting(false), + _comments(new astring), + _comment_number(1), + _add_spaces(false) +{ *this = to_copy; } + +variable_tokenizer::~variable_tokenizer() +{ + WHACK(_separators); + WHACK(_assignments); + WHACK(_implementation); + WHACK(_quotes); + WHACK(_comments); +} + +int variable_tokenizer::symbols() const { return _implementation->symbols(); } + +void variable_tokenizer::set_comment_chars(const astring &comments) +{ *_comments = comments; } + +const astring &variable_tokenizer::assignments() const { return *_assignments; } + +const astring &variable_tokenizer::separators() const { return *_separators; } + +const astring &variable_tokenizer::quotes() const { return *_quotes; } + +bool variable_tokenizer::exists(const astring &name) const +{ return !!_implementation->find(name); } + +void variable_tokenizer::reset() { _implementation->reset(); } + +const string_table &variable_tokenizer::table() const { return *_implementation; } + +string_table &variable_tokenizer::table() { return *_implementation; } + +variable_tokenizer &variable_tokenizer::operator =(const variable_tokenizer &to_copy) +{ + if (this == &to_copy) return *this; + *_implementation = *to_copy._implementation; + *_separators = *to_copy._separators; + *_assignments = *to_copy._assignments; + *_quotes = *to_copy._quotes; + _nesting = to_copy._nesting; + _add_spaces = to_copy._add_spaces; + return *this; +} + +astring variable_tokenizer::find(const astring &name) const +{ + astring *found = _implementation->find(name); + if (!found) return ""; + + // check that the contents are not just our significator of emptiness. + if (found->equal_to(SPECIAL_VALUE)) return ""; + return *found; +} + +bool variable_tokenizer::okay_for_variable_name(char to_check) const +{ + if (!to_check || separator(to_check) || assignment(to_check)) return false; + return true; +} + +bool variable_tokenizer::separator(char to_check) const +{ + // special case allows a CR separator to be either flavor. + if (parser_bits::is_eol(to_check) + && (astring::matches(*_separators, '\n') + || astring::matches(*_separators, '\r')) ) return true; + return astring::matches(*_separators, to_check); +} + +bool variable_tokenizer::assignment(char to_check) const +{ return astring::matches(*_assignments, to_check); } + +bool variable_tokenizer::quote_mark(char to_check) const +{ return astring::matches(*_quotes, to_check); } + +bool variable_tokenizer::comment_char(char to_check) const +{ return astring::matches(*_comments, to_check); } + +#define COOL to_tokenize.length() + // true if the string should continue to be parsed. + +// sets "current" to the first character in the string. +#define CHOP { \ + current = to_tokenize[0]; \ + to_tokenize.zap(0, 0); \ +} + +bool variable_tokenizer::parse(const astring &to_tokenize_in) +{ + FUNCDEF("parse"); + astring to_tokenize(to_tokenize_in); // de-const. +//hmmm: do we need a copy? try scooting based on a current pos. + + astring name, value; // accumulated during the loop. + char current; // the most recent character from to_tokenize. + bool just_ate_blank_line = false; + // records when we handle a blank line as a comment. + + // loop over the string. + while (COOL) { + name.reset(); + value.reset(); + + // pre-processing to remove extra eols and white space in front. + if (is_eol_a_separator() && parser_bits::is_eol(to_tokenize[0])) { + CHOP; + // chop any white space but don't eat any non-white space coming up. + while (COOL && parser_bits::white_space(current)) { + CHOP; + if (!parser_bits::white_space(current)) { + // oops; we ate something we shouldn't have, since it will be + // chopped when we get in the main loop. + to_tokenize.insert(0, astring(current, 1)); + } + } + } + + // chop the first character off for analysis. + CHOP; + + // ignore any white space until we hit a variable or other good stuff. + if (parser_bits::white_space_no_cr(current)) + continue; + + // ignore eol unless they are in separator list. + bool handle_as_comment = false; + if (parser_bits::is_eol(current) && !is_eol_a_separator()) { + continue; + } else if (just_ate_blank_line && parser_bits::is_eol(current)) { + just_ate_blank_line = false; + continue; + } else if (parser_bits::is_eol(current) && is_eol_a_separator()) { +//LOG("found eol and it's a separator here"); + handle_as_comment = true; + } + + if (comment_char(current) || handle_as_comment) { + // set our flag since we are going to eat the end of line in any case. + just_ate_blank_line = true; + // seek all text until next separator. + while (COOL && !separator(current)) { + value += current; + CHOP; + } + // add the item with our ongoing comment number. + a_sprintf name("%s%d", STRTAB_COMMENT_PREFIX, _comment_number); + _implementation->add(name, value); + _comment_number++; // go to next comment number to keep unique. +LOG(astring("got comment: ") + name + " -> " + value); + continue; // got our chunk, keep going. + } + + just_ate_blank_line = false; // reset our flag. + + // skip characters we can't use for a variable name. + if (!okay_for_variable_name(current)) continue; + + // we've found the start of a variable. + while (COOL && okay_for_variable_name(current)) { + // accumulate the variable name. + name += current; + CHOP; // get the next character. + } + if (!COOL) { + // we're at the end of the line, so deal with this situation. + if (!separator(current) && !parser_bits::white_space(current) ) + name += current; // get the character from the end of the line. +LOG(astring("last add: ") + name + " -> " + value); + _implementation->add(name, value); // store what we built. + continue; // skip the rest; we're at the END of the line man. + } + + // skip spaces after variable name. + while (COOL && parser_bits::white_space_no_cr(current)) CHOP; + + bool found_assignment = false; // assume there isn't one. + if (assignment(current)) { + // we found the assignment operator and are starting on the value. + CHOP; // skip the assignment operator. + found_assignment = true; + } + + // skip spaces after the assignment statement. + while (COOL && parser_bits::white_space_no_cr(current)) CHOP; + + // track the quoting that we have to deal with in parsing a value. + stack q_stack(!int(_nesting)); + // create an unbounded stack for nesting. + + while (COOL) { + // check if the current character is a quote. + bool ignore_separator = false; + if (quote_mark(current)) { + if (!q_stack.size()) { + // nothing on the stack yet, so start accumulating. + ignore_separator = true; + q_stack.push(current); + } else if (current == q_stack.top()) { + // we got the end of this quoting. + q_stack.pop(); + // check if we're done with any quotes. if not, we still need to + // ignore the separators. + if (q_stack.size()) + ignore_separator = true; + } else { + // if we are using a bounded stack, it means we only support one + // level of quoting at a time. thus, this quote character simply + // falls in as a regular character. but if we're unbound, then + // we can nest arbitrary levels of quotes. + if (q_stack.kind() == stack::UNBOUNDED) + q_stack.push(current); + // we have something on the stack already so we're still ignoring + // separators. we just don't care about this type of quote. + ignore_separator = true; + } + } else if (q_stack.size()) { + // it's not a quote but we're still trying to chow the matching + // quote character. + ignore_separator = true; + } + + // look for the separator. + if (!ignore_separator && separator(current)) { + break; + } + + // accumulate the value. + value += current; + CHOP; // get the next character. + } + // get the last character if it's relevant. + if (!separator(current) && !parser_bits::white_space(current) ) { + value += current; + } + + if (found_assignment && !value) { + // use our special case for empty values, since there was an assignment + // operator but no value afterwards. + value = SPECIAL_VALUE; + } + + // store the accumulated variable name and value, but only if the name + // is non-empty. otherwise, it's not much of a definition. + if (name.t()) { + // strip spaces at the end of the name. + while (parser_bits::white_space_no_cr(name[name.end()])) + name.zap(name.end(), name.end()); + // strip spaces at the end of the value unless it's the special case. + if (!value.equal_to(SPECIAL_VALUE)) { + while (parser_bits::white_space(value[value.end()])) + value.zap(value.end(), value.end()); + } +LOG(astring("normal add: ") + name + " -> " + value); + _implementation->add(name, value); // store what we built. + just_ate_blank_line = true; // flag that we don't want next EOL. + // reset, just in case. + name.reset(); + value.reset(); + } + } + // currently we just kind of bully through whatever string is provided and do not + // flag any error conditions. but people do like to know if it worked or not. they can + // make their own conclusions if there are not enough variables defined for their needs. + return true; +} + +bool variable_tokenizer::is_eol_a_separator() const +{ + for (int i = 0; i < _separators->length(); i++) { + char sep = _separators->get(i); + // correct the separator for platform when it's the end of the line. + if (parser_bits::is_eol(sep)) return true; + } + return false; +} + +void variable_tokenizer::text_form(astring &accumulator) const +{ + accumulator.reset(); + bool added_sep = false; + for (int i = 0; i < _implementation->symbols(); i++) { + added_sep = false; + if (!string_table::is_comment(_implementation->name(i))) { + // a normal assignment is here. + accumulator += _implementation->name(i); + if (_implementation->operator [](i).t()) { + if (_add_spaces) accumulator += " "; + accumulator += _assignments->get(0); + if (_add_spaces) accumulator += " "; + accumulator += _implementation->operator [](i); + } + } else { + // this one is a comment. just spit out the value. + if (_implementation->operator [](i).t()) + accumulator += _implementation->operator [](i); + } + // correct the separator for platform when it's the end of the line. + if (is_eol_a_separator()) { + accumulator += parser_bits::platform_eol_to_chars(); + } else { + added_sep = true; // record that we put a separator in there. + accumulator += _separators->get(0); + accumulator += ' '; + } + } + // strip the final separator and space back off, if we added them. + if (added_sep) + accumulator.zap(accumulator.end() - 1, accumulator.end()); +} + +astring variable_tokenizer::text_form() const +{ + astring accumulator; + text_form(accumulator); + return accumulator; +} + +} //namespace. + diff --git a/nucleus/library/configuration/variable_tokenizer.h b/nucleus/library/configuration/variable_tokenizer.h new file mode 100644 index 00000000..82b08357 --- /dev/null +++ b/nucleus/library/configuration/variable_tokenizer.h @@ -0,0 +1,181 @@ +#ifndef TOKENIZER_CLASS +#define TOKENIZER_CLASS + +/* +* Name : variable_tokenizer +* Author : Chris Koeritz +** +* Copyright (c) 1997-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include + +namespace configuration { + +//! Manages a bank of textual definitions of variables. +/*! + Manipulates strings containing variable definitions where a variable + is syntactically defined as a name, an assignment operator, and a value. + The string can optionally define many variables by placing a separator + character between the definitions. The assignment and separator are + referred to as sentinels in the following docs. + This class also supports quoted values if the appropriate constructor + is used. +*/ + +class variable_tokenizer : public virtual basis::root_object +{ +public: + enum constraints { DEFAULT_MAX_BITS = 7 }; + + variable_tokenizer(int max_bits = DEFAULT_MAX_BITS); + //!< creates a variable_tokenizer with the default characters. + /*!< this will not look for quote characters. the "max_bits" establishes + the hashing width for the internal table of strings; there will be + 2 ^ "max_bits" of space in the table. the default assignment operator + is '=' and the default separator is ','. */ + + variable_tokenizer(const basis::astring &separator, const basis::astring &assignment, + int max_bits = DEFAULT_MAX_BITS); + //!< creates an empty list of tokens and uses the specified sentinel chars. + /*!< the character that is expected to be between name/value pairs is + "separator". the "assignment" character is expected to be between each + name and its value. note that if the "separator" or "assignment" are more + than one character long, these will be taken as a set of valid characters + that can be used for those purposes. */ + + variable_tokenizer(const basis::astring &separator, const basis::astring &assignment, + const basis::astring "es, bool nesting = true, + int max_bits = DEFAULT_MAX_BITS); + //!< similar to the constructor above, but supports quoting. + /*!< if the "quotes" list is not empty, then those characters will be + treated as quoting characters that must be matched in pairs. inside a + quote, separators are ignored. if "nesting" is not true, then only one + level of quotes will be considered; the occurrence of other types of + quotes will be ignored until the original type is completed. */ + + variable_tokenizer(const variable_tokenizer &to_copy); + //!< builds a variable_tokenizer that is identical to "to_copy". + + virtual ~variable_tokenizer(); + + DEFINE_CLASS_NAME("variable_tokenizer"); + + void set_comment_chars(const basis::astring &comments); + //!< establishes a set of characters in "comments" as the comment items. + /*!< comments will be specially handled by being added to the string table + with the comment prefix. this allows them to be regenerated uniquely + later. */ + + variable_tokenizer &operator =(const variable_tokenizer &to_copy); + //!< makes this variable_tokenizer identical to "to_copy". + + int symbols() const; + //!< returns the number of entries in the variable_tokenizer. + + void reset(); + //!< clears all of the entries out. + + const structures::string_table &table() const; + //!< provides a constant peek at the string_table holding the values. + structures::string_table &table(); + //!< provides direct access to the string_table holding the values. + +//fix these docs. + bool parse(const basis::astring &to_tokenize); + //!< parses the string using our established sentinel characters. + /*!< attempts to snag as many value/pairs from "to_tokenize" as are + possible by using the current separator and assignment characters. + E.G.: if the separator is ';' and the assignment character + is '=', then one's string would look something like: @code + TEMP=c:\tmp; GLOB=c:\glob.exe; .... @endcode + whitespace is ignored if it's found (1) after a separator and before + the next variable name, (2) after the variable name and before the + assignment character, (3) after the assignment character and before the + value. this unfortunately implies that white space cannot begin or end + a value. + NOTE: unpredictable results will occur: if one's variables are + improperly formed, if assignment operators are missing or misplaced, + or if the separator character is used within the value. + NOTE: carriage returns are considered white-space and can exist in the + string as described above. + NOTE: parse is additive; if multiple calls to parse() occur, then the + symbol_table will be built from the most recent values found in the + parameters to parse(). if this is not desired, the symbol table's + reset() function can be used to empty out all variables. */ + + basis::astring find(const basis::astring &name) const; + //!< locates the value for a variable named "name" if it exists. + /*!< if "name" doesn't exist, then it returns an empty string. note that + an empty string might also indicate that the value is blank; locate is the + way to tell if a field is really missing. also note that when a variable + name is followed by an assignment operator and an empty value (e.g., + "avversione=" has no value), then a value of a single space character + will be stored. this ensures that the same format is used on the + output side, but it also means that if you access the table directly, + then you will get a space as the value. however, this function returns + an empty string for those entries to keep consistent with expectations. */ + + bool exists(const basis::astring &name) const; + //!< returns true if the "name" exists in the variable_tokenizer. + + basis::astring text_form() const; + //!< creates a new token list as a string of text. + /*!< the first separator and assignment characters in each set are used + to generate it. note that the whitespace that existed in the original + parsed string might not be exactly the same in the generated string. */ + void text_form(basis::astring &to_fill) const; + //!< like text_form() above, but stores into "to_fill". + + // dictates whether the output will have spaces between the assignment + // character and the key name and value. default is to not add them. + bool add_spaces() const { return _add_spaces; } + void add_spaces(bool add_them) { _add_spaces = add_them; } + + bool okay_for_variable_name(char to_check) const; + //!< true if "to_check" is a valid variable name character. + /*!< this includes any characters besides separators and assignments. */ + + const basis::astring &assignments() const; + //!< provides a peek at the assignments list. + const basis::astring &separators() const; + //!< provides a peek at the separators list. + const basis::astring "es() const; + //!< provides a peek at the quotes list. + + bool assignment(char to_check) const; + //!< true if "to_check" is a valid assignment operator. + + bool separator(char to_check) const; + //!< true if "to_check" is a valid separator. + + bool comment_char(char to_check) const; + //!< true if "to_check" is a registered comment character. + + bool is_eol_a_separator() const; + //!< reports whether any of the separators are an EOL character. + + bool quote_mark(char to_check) const; + //!< true if "to_check" is a member of the quotes list. + +private: + structures::string_table *_implementation; //!< holds the parsed values. + basis::astring *_assignments; //!< separates name from value. + basis::astring *_separators; //!< separates name/value pairs from other pairs. + basis::astring *_quotes; //!< the characters that are used for quoting. + bool _nesting; //!< if true, we nest arbitrary levels of quotes. + basis::astring *_comments; //!< if non-empty, characters that begin comments. + int _comment_number; //!< automatically incremented for use in comment tags. + bool _add_spaces; //!< records whether we add spaces around the assignment. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/crypto/blowfish_crypto.cpp b/nucleus/library/crypto/blowfish_crypto.cpp new file mode 100644 index 00000000..1e8ee443 --- /dev/null +++ b/nucleus/library/crypto/blowfish_crypto.cpp @@ -0,0 +1,281 @@ +/*****************************************************************************\ +* * +* Name : blowfish encryption * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "blowfish_crypto.h" +#include "ssl_init.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; + +namespace crypto { + +const int FUDGE = 128; + // extra space for the cipher's block size. blowfish is only 8 bytes for + // the cipher block size, but we ensure there will definitely be no + // problems. + +#undef set_key + // get rid of a macro we don't want. + +#undef LOG +#define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t) + +//#define DEBUG_BLOWFISH + // uncomment for noisier version. + +#ifdef DEBUG_BLOWFISH + // this macro checks on the validity of the key sizes (in bits). + #define DISCUSS_KEY_SIZE(key_size) \ + if (key_size < minimum_key_size()) { \ + continuable_error(static_class_name(), func, \ + a_sprintf("key size (%d bits) is less than minimum key size %d.", \ + key_size, minimum_key_size())); \ + } \ + if (key_size > maximum_key_size()) { \ + continuable_error(static_class_name(), func, \ + a_sprintf("key size (%d bits) is greater than maximum key size %d.", \ + key_size, maximum_key_size())); \ + } + + // this macro checks that the key in the byte array has enough bytes for + // the key size bits. + #define DISCUSS_PROVIDED_KEY(key_size, key) \ + if (key.length() * BITS_PER_BYTE < key_size) { \ + continuable_error(static_class_name(), func, \ + a_sprintf("key array length (%d) is less than required by key size " \ + "(%d bits).", key.length(), key_size)); \ + } +#else + #define DISCUSS_PROVIDED_KEY(key_size, key) + #define DISCUSS_KEY_SIZE(key_size) +#endif + +blowfish_crypto::blowfish_crypto(int key_size) +: _key_size(key_size), + _key(new byte_array) +{ +// FUNCDEF("constructor [int]"); + static_ssl_initializer(); + DISCUSS_KEY_SIZE(key_size); + if (key_size < minimum_key_size()) + _key_size = minimum_key_size(); + if (key_size > maximum_key_size()) + _key_size = maximum_key_size(); + generate_key(_key_size, *_key); +} + +blowfish_crypto::blowfish_crypto(const byte_array &key, int key_size) +: _key_size(key_size), + _key(new byte_array(key)) +{ +// FUNCDEF("constructor [byte_array/int]"); + // any problems with the key provided are horrid. they will yield a + // non-working blowfish object. + DISCUSS_KEY_SIZE(key_size); + DISCUSS_PROVIDED_KEY(key_size, key); + static_ssl_initializer(); +} + +blowfish_crypto::blowfish_crypto(const blowfish_crypto &to_copy) +: root_object(), + _key_size(to_copy._key_size), + _key(new byte_array(*to_copy._key)) +{ static_ssl_initializer(); } + +blowfish_crypto::~blowfish_crypto() +{ + WHACK(_key); +} + +int blowfish_crypto::key_size() const { return _key_size; } + +const byte_array &blowfish_crypto::get_key() const { return *_key; } + +int blowfish_crypto::minimum_key_size() { return 64; } + +int blowfish_crypto::maximum_key_size() { return 448; } + +blowfish_crypto &blowfish_crypto::operator = (const blowfish_crypto &to_copy) +{ + if (this == &to_copy) return *this; + _key_size = to_copy._key_size; + *_key = *to_copy._key; + return *this; +} + +bool blowfish_crypto::set_key(const byte_array &new_key, int key_size) +{ +// FUNCDEF("set_key"); + if (!new_key.length()) return false; + DISCUSS_KEY_SIZE(key_size); + DISCUSS_PROVIDED_KEY(key_size, new_key); + if ( (key_size < minimum_key_size()) || (key_size > maximum_key_size()) ) + return false; + if (new_key.length() * BITS_PER_BYTE < key_size) return false; + _key_size = key_size; + *_key = new_key; + return true; +} + +void blowfish_crypto::generate_key(int size, byte_array &new_key) +{ +// FUNCDEF("generate_key"); + DISCUSS_KEY_SIZE(size); + if (size < minimum_key_size()) + size = minimum_key_size(); + else if (size > maximum_key_size()) + size = maximum_key_size(); + int bytes = size / BITS_PER_BYTE; // calculate the number of bytes needed. + if (size % BITS_PER_BYTE) bytes++; // add one for non-integral portion. + new_key.reset(bytes); + for (int i = 0; i < bytes; i++) + new_key[i] = static_ssl_initializer().randomizer().inclusive(0, 255); +} + +SAFE_STATIC(mutex, __vector_init_lock, ) + +const byte_array &blowfish_crypto::init_vector() +{ + auto_synchronizer locking(__vector_init_lock()); + static byte_array to_return(EVP_MAX_IV_LENGTH); + static bool initted = false; + if (!initted) { + for (int i = 0; i < EVP_MAX_IV_LENGTH; i++) + to_return[i] = 214 - i; + initted = true; + } + return to_return; +} + +bool blowfish_crypto::encrypt(const byte_array &source, + byte_array &target) const +{ + FUNCDEF("encrypt"); + target.reset(); + if (!_key->length() || !source.length()) return false; + bool to_return = true; + + // initialize an encoding session. + EVP_CIPHER_CTX session; + EVP_CIPHER_CTX_init(&session); + EVP_EncryptInit_ex(&session, EVP_bf_cbc(), NIL, _key->observe(), + init_vector().observe()); + EVP_CIPHER_CTX_set_key_length(&session, _key_size); + + // allocate temporary space for encrypted data. + byte_array encoded(source.length() + FUDGE); + + // encrypt the entire source buffer. + int encoded_len = 0; + int enc_ret = EVP_EncryptUpdate(&session, encoded.access(), &encoded_len, + source.observe(), source.length()); + if (enc_ret != 1) { + continuable_error(class_name(), func, a_sprintf("encryption failed, " + "result=%d.", enc_ret)); + to_return = false; + } else { + // chop any extra space off. +// LOG(a_sprintf("encrypting bytes %d to %d.\n", i, edge)); + encoded.zap(encoded_len, encoded.last()); + target = encoded; + } + + // only add padding if we succeeded with the encryption. + if (enc_ret == 1) { + // finalize the encryption. + encoded.reset(FUDGE); // reinflate for padding. + int pad_len = 0; + enc_ret = EVP_EncryptFinal_ex(&session, encoded.access(), &pad_len); + if (enc_ret != 1) { + continuable_error(class_name(), func, a_sprintf("finalizing encryption " + "failed, result=%d.", enc_ret)); + to_return = false; + } else { +// LOG(a_sprintf("padding added %d bytes.\n", pad_len)); + encoded.zap(pad_len, encoded.last()); + target += encoded; + } + } + + EVP_CIPHER_CTX_cleanup(&session); + return to_return; +} + +bool blowfish_crypto::decrypt(const byte_array &source, + byte_array &target) const +{ + FUNCDEF("decrypt"); + target.reset(); + if (!_key->length() || !source.length()) return false; + bool to_return = true; + EVP_CIPHER_CTX session; + EVP_CIPHER_CTX_init(&session); +//LOG(a_sprintf("key size %d bits.\n", BITS_PER_BYTE * _key->length())); + EVP_DecryptInit_ex(&session, EVP_bf_cbc(), NIL, _key->observe(), + init_vector().observe()); + EVP_CIPHER_CTX_set_key_length(&session, _key_size); + + // allocate enough space for decoded bytes. + byte_array decoded(source.length() + FUDGE); + + int decoded_len = 0; + int dec_ret = EVP_DecryptUpdate(&session, decoded.access(), &decoded_len, + source.observe(), source.length()); + if (dec_ret != 1) { + continuable_error(class_name(), func, "decryption failed."); + to_return = false; + } else { +// LOG(a_sprintf(" decrypted size in bytes is %d.\n", decoded_len)); + decoded.zap(decoded_len, decoded.last()); + target = decoded; + } + + // only process padding if the first part of decryption succeeded. + if (dec_ret == 1) { + decoded.reset(FUDGE); // reinflate for padding. + int pad_len = 0; + dec_ret = EVP_DecryptFinal_ex(&session, decoded.access(), &pad_len); +// LOG(a_sprintf("padding added %d bytes.\n", pad_len)); + if (dec_ret != 1) { + continuable_error(class_name(), func, a_sprintf("finalizing decryption " + "failed, result=%d, padlen=%d, target had %d bytes.", dec_ret, + pad_len, target.length())); + to_return = false; + } else { + int dec_size = pad_len; + decoded.zap(dec_size, decoded.last()); + target += decoded; + } + } + + EVP_CIPHER_CTX_cleanup(&session); + return to_return; +} + +} //namespace. + + diff --git a/nucleus/library/crypto/blowfish_crypto.h b/nucleus/library/crypto/blowfish_crypto.h new file mode 100644 index 00000000..42a62503 --- /dev/null +++ b/nucleus/library/crypto/blowfish_crypto.h @@ -0,0 +1,90 @@ +#ifndef BLOWFISH_CRYPTO_CLASS +#define BLOWFISH_CRYPTO_CLASS + +/*****************************************************************************\ +* * +* Name : blowfish encryption * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace crypto { + +//! Provides BlowFish encryption on byte_arrays using the OpenSSL package. + +class blowfish_crypto : public virtual basis::root_object +{ +public: + blowfish_crypto(int key_size); + //!< this will create a new random key of the "key_size", in bits. + /*!< the valid sizes are from 64 bits to 448 bits (we are forcing a + higher minimum than the published algorithm because we have found smaller + keys to be unreliable during decryption. keys of 168 bits and larger + should be very secure. it is said that if a billion computers each tried + a billion keys a second, then a 168 bit key would take 10 * 10^24 years + to break (using brute force). this is essentially unbreakable since the + age of the universe is only 10 * 10^9 years so far. */ + + blowfish_crypto(const basis::byte_array &key, int key_size); + //!< uses a pre-existing "key". + + blowfish_crypto(const blowfish_crypto &to_copy); //!< copy constructor. + + virtual ~blowfish_crypto(); + + blowfish_crypto &operator = (const blowfish_crypto &to_copy); + + DEFINE_CLASS_NAME("blowfish_crypto"); + + int key_size() const; // returns the size of our key, in bits. + + static int minimum_key_size(); + //!< returns the minimum key size (in bits) supported here. + static int maximum_key_size(); + //!< returns the maximum key size (in bits) supported here. + + const basis::byte_array &get_key() const; //!< returns our current key. + + bool set_key(const basis::byte_array &new_key, int key_size); + //!< sets the encryption key to "new_key". + + static void generate_key(int size, basis::byte_array &new_key); + //!< creates a "new_key" of the "size" (in bits) specified. + + bool encrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< encrypts the "source" array into the "target" array. + + bool decrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< decrypts the "target" array from the encrypted "source" array. + + // seldom-needed methods... + + static const basis::byte_array &init_vector(); + //!< returns the initialization vector that is used by this class. + /*!< decryption of chunks that were encrypted by this class will require + the same init vector as this function returns. this is mainly provided + for third-party applications that want to be able to decrypt interoperably + with this class. if you are creating such an application but for some + reason cannot run this class in order to invoke this method, the vector + is created by the algorithm in this class's implementation file + (currently named blowfish_crypto.cpp). */ + +private: + int _key_size; //!< number of bits in the key. + basis::byte_array *_key; //!< our secret key. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/crypto/makefile b/nucleus/library/crypto/makefile new file mode 100644 index 00000000..97c7376a --- /dev/null +++ b/nucleus/library/crypto/makefile @@ -0,0 +1,12 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +TYPE = library +PROJECT = crypto +SOURCE = blowfish_crypto.cpp rsa_crypto.cpp ssl_init.cpp +USE_SSL = t +TARGETS = crypto.lib + +include cpp/rules.def + diff --git a/nucleus/library/crypto/rsa_crypto.cpp b/nucleus/library/crypto/rsa_crypto.cpp new file mode 100644 index 00000000..f50d3076 --- /dev/null +++ b/nucleus/library/crypto/rsa_crypto.cpp @@ -0,0 +1,315 @@ +/*****************************************************************************\ +* * +* Name : RSA public key encryption * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Supports public (and private) key encryption and decryption using the * +* OpenSSL package's support for RSA encryption. * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "rsa_crypto.h" +#include "ssl_init.h" + +#include +#include +#include +#include + +#include +#include + +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; + +namespace crypto { + +// notes from openssl docs: length to be encrypted in a chunk must be less than +// RSA_size(rsa) - 11 for the PKCS #1 v1.5 based padding modes, less than +// RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING and exactly RSA_size(rsa) +// for RSA_NO_PADDING. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +//nice printing method... RSA_print_fp(stdout, private_key, 0); + +rsa_crypto::rsa_crypto(int key_size) +: _key(NIL) +{ + _key = generate_key(key_size); // generate_key initializes ssl for us. +} + +rsa_crypto::rsa_crypto(const byte_array &key) +: _key(NIL) +{ + static_ssl_initializer(); + byte_array key_copy = key; + set_key(key_copy); +} + +rsa_crypto::rsa_crypto(rsa_st *key) +: _key(NIL) +{ + static_ssl_initializer(); + set_key(key); +} + +rsa_crypto::rsa_crypto(const rsa_crypto &to_copy) +: root_object(), + _key(NIL) +{ + static_ssl_initializer(); + set_key(to_copy._key); +} + +rsa_crypto::~rsa_crypto() +{ + RSA_free(_key); +} + +const rsa_crypto &rsa_crypto::operator = (const rsa_crypto &to_copy) +{ + if (this == &to_copy) return *this; + set_key(to_copy._key); + return *this; +} + +rsa_st *rsa_crypto::generate_key(int key_size) +{ + FUNCDEF("generate_key"); + if (key_size < 4) key_size = 4; // laughable lower default. + static_ssl_initializer(); + rsa_st *to_return = RSA_generate_key(key_size, 65537, NIL, NIL); + if (!to_return) { + continuable_error(static_class_name(), func, + a_sprintf("failed to generate a key of %d bits.", key_size)); + } + return to_return; +} + +bool rsa_crypto::check_key(rsa_st *key) { return RSA_check_key(key) == 1; } + +bool rsa_crypto::set_key(byte_array &key) +{ + FUNCDEF("set_key [byte_array]"); + if (!key.length()) return false; + if (_key) RSA_free(_key); + _key = RSA_new(); + abyte type; + if (!structures::detach(key, type)) return false; + if ( (type != 'r') && (type != 'u') ) return false; + // get the public key bits first. + byte_array n; + if (!structures::detach(key, n)) return false; + _key->n = BN_bin2bn(n.access(), n.length(), NIL); + if (!_key->n) return false; + byte_array e; + if (!structures::detach(key, e)) return false; + _key->e = BN_bin2bn(e.access(), e.length(), NIL); + if (!_key->e) return false; + if (type == 'u') return true; // done with public key. + + // the rest is for a private key. + byte_array d; + if (!structures::detach(key, d)) return false; + _key->d = BN_bin2bn(d.access(), d.length(), NIL); + if (!_key->d) return false; + byte_array p; + if (!structures::detach(key, p)) return false; + _key->p = BN_bin2bn(p.access(), p.length(), NIL); + if (!_key->p) return false; + byte_array q; + if (!structures::detach(key, q)) return false; + _key->q = BN_bin2bn(q.access(), q.length(), NIL); + if (!_key->q) return false; + byte_array dmp1; + if (!structures::detach(key, dmp1)) return false; + _key->dmp1 = BN_bin2bn(dmp1.access(), dmp1.length(), NIL); + if (!_key->dmp1) return false; + byte_array dmq1; + if (!structures::detach(key, dmq1)) return false; + _key->dmq1 = BN_bin2bn(dmq1.access(), dmq1.length(), NIL); + if (!_key->dmq1) return false; + byte_array iqmp; + if (!structures::detach(key, iqmp)) return false; + _key->iqmp = BN_bin2bn(iqmp.access(), iqmp.length(), NIL); + if (!_key->iqmp) return false; + int check = RSA_check_key(_key); + if (check != 1) { + continuable_error(static_class_name(), func, "failed to check the private " + "portion of the key!"); + return false; + } + + return true; +} + +bool rsa_crypto::set_key(rsa_st *key) +{ + FUNCDEF("set_key [rsa_st]"); + if (!key) return NIL; + // test the incoming key. + int check = RSA_check_key(key); + if (check != 1) return false; + // clean out the old key. + if (_key) RSA_free(_key); + _key = RSAPrivateKey_dup(key); + if (!_key) { + continuable_error(static_class_name(), func, "failed to create a " + "duplicate of the key!"); + return false; + } + return true; +} + +bool rsa_crypto::public_key(byte_array &pubkey) const +{ +// FUNCDEF("public_key"); + if (!_key) return false; + structures::attach(pubkey, abyte('u')); // signal a public key. + // convert the two public portions into binary. + byte_array n(BN_num_bytes(_key->n)); + int ret = BN_bn2bin(_key->n, n.access()); + byte_array e(BN_num_bytes(_key->e)); + ret = BN_bn2bin(_key->e, e.access()); + // pack those two chunks. + structures::attach(pubkey, n); + structures::attach(pubkey, e); + return true; +} + +bool rsa_crypto::private_key(byte_array &privkey) const +{ +// FUNCDEF("private_key"); + if (!_key) return false; + int posn = privkey.length(); + bool worked = public_key(privkey); // get the public pieces first. + if (!worked) return false; + privkey[posn] = abyte('r'); // switch public key flag to private. + // convert the multiple private portions into binary. + byte_array d(BN_num_bytes(_key->d)); + int ret = BN_bn2bin(_key->d, d.access()); + byte_array p(BN_num_bytes(_key->p)); + ret = BN_bn2bin(_key->p, p.access()); + byte_array q(BN_num_bytes(_key->q)); + ret = BN_bn2bin(_key->q, q.access()); + byte_array dmp1(BN_num_bytes(_key->dmp1)); + ret = BN_bn2bin(_key->dmp1, dmp1.access()); + byte_array dmq1(BN_num_bytes(_key->dmq1)); + ret = BN_bn2bin(_key->dmq1, dmq1.access()); + byte_array iqmp(BN_num_bytes(_key->iqmp)); + ret = BN_bn2bin(_key->iqmp, iqmp.access()); + // pack all those in now. + structures::attach(privkey, d); + structures::attach(privkey, p); + structures::attach(privkey, q); + structures::attach(privkey, dmp1); + structures::attach(privkey, dmq1); + structures::attach(privkey, iqmp); + return true; +} + +bool rsa_crypto::public_encrypt(const byte_array &source, + byte_array &target) const +{ +// FUNCDEF("public_encrypt"); + target.reset(); + if (!source.length()) return false; + const int max_chunk = RSA_size(_key) - 12; + + byte_array encoded(RSA_size(_key)); + for (int i = 0; i < source.length(); i += max_chunk) { + int edge = i + max_chunk - 1; + if (edge > source.last()) + edge = source.last(); + int next_chunk = edge - i + 1; + RSA_public_encrypt(next_chunk, &source[i], + encoded.access(), _key, RSA_PKCS1_PADDING); + target += encoded; + } + return true; +} + +bool rsa_crypto::private_decrypt(const byte_array &source, + byte_array &target) const +{ +// FUNCDEF("private_decrypt"); + target.reset(); + if (!source.length()) return false; + const int max_chunk = RSA_size(_key); + + byte_array decoded(max_chunk); + for (int i = 0; i < source.length(); i += max_chunk) { + int edge = i + max_chunk - 1; + if (edge > source.last()) + edge = source.last(); + int next_chunk = edge - i + 1; + int dec_size = RSA_private_decrypt(next_chunk, &source[i], + decoded.access(), _key, RSA_PKCS1_PADDING); + if (dec_size < 0) return false; // that didn't work. + decoded.zap(dec_size, decoded.last()); + target += decoded; + decoded.reset(max_chunk); + } + return true; +} + +bool rsa_crypto::private_encrypt(const byte_array &source, + byte_array &target) const +{ +// FUNCDEF("private_encrypt"); + target.reset(); + if (!source.length()) return false; + const int max_chunk = RSA_size(_key) - 12; + + byte_array encoded(RSA_size(_key)); + for (int i = 0; i < source.length(); i += max_chunk) { + int edge = i + max_chunk - 1; + if (edge > source.last()) + edge = source.last(); + int next_chunk = edge - i + 1; + RSA_private_encrypt(next_chunk, &source[i], + encoded.access(), _key, RSA_PKCS1_PADDING); + target += encoded; + } + return true; +} + +bool rsa_crypto::public_decrypt(const byte_array &source, + byte_array &target) const +{ +// FUNCDEF("public_decrypt"); + target.reset(); + if (!source.length()) return false; + const int max_chunk = RSA_size(_key); + + byte_array decoded(max_chunk); + for (int i = 0; i < source.length(); i += max_chunk) { + int edge = i + max_chunk - 1; + if (edge > source.last()) + edge = source.last(); + int next_chunk = edge - i + 1; + int dec_size = RSA_public_decrypt(next_chunk, &source[i], + decoded.access(), _key, RSA_PKCS1_PADDING); + if (dec_size < 0) return false; // that didn't work. + decoded.zap(dec_size, decoded.last()); + target += decoded; + decoded.reset(max_chunk); + } + return true; +} + +} //namespace. + diff --git a/nucleus/library/crypto/rsa_crypto.h b/nucleus/library/crypto/rsa_crypto.h new file mode 100644 index 00000000..e43f1bdb --- /dev/null +++ b/nucleus/library/crypto/rsa_crypto.h @@ -0,0 +1,102 @@ +#ifndef RSA_CRYPTO_CLASS +#define RSA_CRYPTO_CLASS + +/*****************************************************************************\ +* * +* Name : RSA public key encryption * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +// forward. +struct rsa_st; + +namespace crypto { + +//! Supports public key encryption and decryption. +/*! + This class uses the OpenSSL package's support for RSA encryption. +*/ + +class rsa_crypto : public virtual basis::nameable +{ +public: + rsa_crypto(int key_size); + //!< constructs using a randomized private key of the "key_size". + /*!< the "key_size" must be at least 1024 bits for acceptable security. + smaller keys are considered insecure. */ + + rsa_crypto(const basis::byte_array &key); + //!< constructs with the specified "key" as our private key. + /*!< the "key" is used for encryption rather than generating a random one. + the key is only valid if it was created with this class. also, if the key + is a public key, then only the public_encryption and public_decryption + methods will be available. */ + + rsa_crypto(rsa_st *key); + //!< starts with a pre-existing "key" in the low-level form. + + rsa_crypto(const rsa_crypto &to_copy); + + virtual ~rsa_crypto(); + + const rsa_crypto &operator = (const rsa_crypto &to_copy); + + DEFINE_CLASS_NAME("rsa_crypto"); + + bool set_key(basis::byte_array &key); + //!< resets this object's key to "key". + /*!< the key is only valid if this class created it. note: the "key" + is destructively consumed during the set method; do not pass in your + only copy. */ + + bool set_key(rsa_st *key); + //!< sets our new "key". + /*!< this must be a valid key created via the RSA algorithms. */ + + bool check_key(rsa_st *key); + //!< checks the RSA "key" provided for validity. + + bool public_encrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< encrypts "source" using our public key and stores it in "target". + /*!< public_encrypt and private_decrypt are a pair. an untrusted user can + encrypt with the public key and only the possessor of the private key + should be able to decrypt it. */ + bool private_decrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< decrypts "source" using our private key and stores it in "target". + + bool private_encrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< encrypts "source" using our private key and stores it in "target". + /*!< private_encrypt and public_decrypt are also a pair. the trusted + user with the private key can create encrypted chunks that anyone with + the public key can decrypt. */ + bool public_decrypt(const basis::byte_array &source, basis::byte_array &target) const; + //!< decrypts "source" using our public key and stores it in "target". + + bool public_key(basis::byte_array &pubkey) const; + //!< makes a copy of the public key held here. + bool private_key(basis::byte_array &privkey) const; + //!< makes a copy of the private key held here. + /*!< the private key should never be exposed to anyone else. */ + + static rsa_st *generate_key(int key_size); + //!< creates a random RSA key using the lower-level openssl methods. + +private: + rsa_st *_key; //!< our internal key. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/crypto/ssl_init.cpp b/nucleus/library/crypto/ssl_init.cpp new file mode 100644 index 00000000..f32da3a4 --- /dev/null +++ b/nucleus/library/crypto/ssl_init.cpp @@ -0,0 +1,72 @@ +/*****************************************************************************\ +* * +* Name : SSL initialization helper * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ssl_init.h" + +#include +#include +#include + +#include +#include +#include + +using namespace basis; +using namespace mathematics; +using namespace structures; + +namespace crypto { + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +const int SEED_SIZE = 100; + // the size of the random seed that we'll use. + +// our global initialization object. +SAFE_STATIC_CONST(ssl_init, static_ssl_initializer, ) + +//#define DEBUG_SSL + // uncomment to cause more debugging information to be generated, plus + // more checking to be performed in the SSL support. + +ssl_init::ssl_init() +: c_rando() +{ +#ifdef DEBUG_SSL + CRYPTO_malloc_debug_init(); + CRYPTO_dbg_set_options(V_CRYPTO_MDEBUG_ALL); + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); +#endif + RAND_seed(random_bytes(SEED_SIZE).observe(), SEED_SIZE); +} + +ssl_init::~ssl_init() +{ + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + CRYPTO_mem_leaks_fp(stderr); +} + +const chaos &ssl_init::randomizer() const { return c_rando; } + +byte_array ssl_init::random_bytes(int length) const +{ + byte_array seed; + for (int i = 0; i < length; i++) + seed += abyte(c_rando.inclusive(0, 255)); + return seed; +} + +} //namespace. + diff --git a/nucleus/library/crypto/ssl_init.h b/nucleus/library/crypto/ssl_init.h new file mode 100644 index 00000000..7357bd8c --- /dev/null +++ b/nucleus/library/crypto/ssl_init.h @@ -0,0 +1,55 @@ +#ifndef SSL_INIT_CLASS +#define SSL_INIT_CLASS + +/*****************************************************************************\ +* * +* Name : SSL initialization helper * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace crypto { + +//! provides some initialization for the RSA and blowfish crypto. +/*! + This class does the SSL initialization needed before any functions can + be used. It also sets up the random seed for SSL. NOTE: you should never + need to use this class directly; just use the accessor function at the + very bottom and it will be managed globally for the entire program. +*/ + +class ssl_init : public virtual basis::nameable +{ +public: + ssl_init(); + ~ssl_init(); + + DEFINE_CLASS_NAME("ssl_init"); + + basis::byte_array random_bytes(int length) const; + //!< can be used to generate a random array of "length" bytes. + + const mathematics::chaos &randomizer() const; + //!< provides a random number generator for any encryption routines. + +private: + mathematics::chaos c_rando; //!< used for generating random numbers. +}; + +extern const ssl_init &static_ssl_initializer(); + //!< the main method for accessing the SSL initialization support. + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/byte_filer.cpp b/nucleus/library/filesystem/byte_filer.cpp new file mode 100644 index 00000000..7aa05d78 --- /dev/null +++ b/nucleus/library/filesystem/byte_filer.cpp @@ -0,0 +1,236 @@ +/*****************************************************************************\ +* * +* Name : byte_filer * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_filer.h" + +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include +#endif +#ifdef __WIN32__ + #include +#endif + +//#define DEBUG_BYTE_FILER + // uncomment for noisy version of class. + +using namespace basis; + +namespace filesystem { + +const size_t BTFL_FILE_TELL_LIMIT = size_t(2) * size_t(GIGABYTE); + // the largest a long integer can represent in the tell system call. + +class file_hider +{ +public: + FILE *fp; // the real file pointer. + + file_hider() : fp(NIL) {} +}; + +////////////// + +byte_filer::byte_filer() +: _handle(new file_hider), + _filename(new astring), + _auto_close(true) +{} + +byte_filer::byte_filer(const astring &filename, const astring &perms) +: _handle(new file_hider), + _filename(new astring), + _auto_close(true) +{ open(filename, perms); } + +byte_filer::byte_filer(const char *filename, const char *perms) +: _handle(new file_hider), + _filename(new astring), + _auto_close(true) +{ open(filename, perms); } + +byte_filer::byte_filer(bool auto_close, void *handle) +: _handle(new file_hider), + _filename(new astring), + _auto_close(auto_close) +{ + if (handle) { + _handle->fp = (FILE *)handle; + } +} + +byte_filer::~byte_filer() { close(); WHACK(_handle); WHACK(_filename); } + +astring byte_filer::filename() const { return *_filename; } + +size_t byte_filer::file_size_limit() { return BTFL_FILE_TELL_LIMIT; } + +bool byte_filer::open(const astring &filename, const astring &perms) +{ + close(); + _auto_close = true; // reset since we know we're opening this. + *_filename = filename; +#ifndef __WIN32__ + _handle->fp = filename.t()? fopen(filename.s(), perms.s()) : NIL; +#else + _handle->fp = filename.t()? _wfopen((wchar_t *)(UTF16 *)transcode_to_utf16(filename), + (wchar_t *)(UTF16 *)transcode_to_utf16(perms)) : NIL; + +#ifdef DEBUG_BYTE_FILER + if (!_handle->fp) + wprintf((wchar_t *)(UTF16 *)transcode_to_utf16("could not open: %ls\n"), + (wchar_t *)(UTF16 *)transcode_to_utf16(filename)); +#endif + +#endif + return good(); +} + +void byte_filer::close() +{ + *_filename = ""; + if (_auto_close && _handle->fp) fclose(_handle->fp); + _handle->fp = NIL; +} + +bool byte_filer::good() { return !!_handle->fp; } + +size_t byte_filer::tell() +{ + if (!_handle->fp) return 0; + long to_return = ::ftell(_handle->fp); + if (to_return == -1) { + // if we couldn't get the size, either the file isn't there or the size + // is too big for our OS to report. +///printf(a_sprintf("failed to tell size, calling it %.0f, and one plus that is %.0f\n", double(BTFL_FILE_TELL_LIMIT), double(long(long(BTFL_FILE_TELL_LIMIT) + 1))).s()); + if (good()) return BTFL_FILE_TELL_LIMIT; + else return 0; + } + return size_t(to_return); +} + +void *byte_filer::file_handle() { return _handle->fp; } + +bool byte_filer::eof() { return !_handle->fp ? true : !!feof(_handle->fp); } + +int byte_filer::read(abyte *buff, int size) +{ return !_handle->fp ? 0 : int(::fread((char *)buff, 1, size, _handle->fp)); } + +int byte_filer::write(const abyte *buff, int size) +{ return !_handle->fp ? 0 : int(::fwrite((char *)buff, 1, size, _handle->fp)); } + +int byte_filer::read(byte_array &buff, int desired_size) +{ + buff.reset(desired_size); + int to_return = read(buff.access(), desired_size); + buff.zap(to_return, buff.length() - 1); + return to_return; +} + +int byte_filer::write(const byte_array &buff) +{ return write(buff.observe(), buff.length()); } + +size_t byte_filer::length() +{ + size_t current_posn = tell(); + seek(0, FROM_END); // jump to end of file. + size_t file_size = tell(); // get position. + seek(int(current_posn), FROM_START); // jump back to previous place. + return file_size; +} + +int byte_filer::read(astring &s, int desired_size) +{ + s.pad(desired_size + 2); + int found = read((abyte *)s.observe(), desired_size); + if (non_negative(found)) s[found] = '\0'; + s.shrink(); + return found; +} + +int byte_filer::write(const astring &s, bool add_null) +{ + int len = s.length(); + if (add_null) len++; + return write((abyte *)s.observe(), len); +} + +void byte_filer::flush() +{ + if (!_handle->fp) return; + ::fflush(_handle->fp); +} + +bool byte_filer::truncate() +{ + flush(); + int fnum = fileno(_handle->fp); +#ifdef __WIN32__ + return SetEndOfFile((HANDLE)_get_osfhandle(fnum)); +#else + size_t posn = tell(); + // if we're at the highest point we can be, we no longer trust our + // ability to truncate properly. + if (posn >= file_size_limit()) + return false; + return !ftruncate(fnum, posn); +#endif +} + +bool byte_filer::seek(int where, origins origin) +{ + if (!_handle->fp) return false; + int real_origin; + switch (origin) { + case FROM_START: real_origin = SEEK_SET; break; + case FROM_END: real_origin = SEEK_END; break; + case FROM_CURRENT: real_origin = SEEK_CUR; break; + default: return false; // not a valid choice. + } + int ret = ::fseek(_handle->fp, where, real_origin); + return !ret; +} + +int byte_filer::getline(abyte *buff, int desired_size) +{ + if (!_handle->fp) return 0; + char *ret = ::fgets((char *)buff, desired_size, _handle->fp); + return !ret? 0 : int(strlen((char *)buff)) + 1; +} + +int byte_filer::getline(byte_array &buff, int desired_size) +{ + buff.reset(desired_size + 1); + return getline(buff.access(), desired_size); +} + +int byte_filer::getline(astring &buff, int desired_size) +{ + buff.pad(desired_size + 1); + int to_return = getline((abyte *)buff.access(), desired_size); + if (non_negative(to_return)) buff[to_return] = '\0'; + buff.shrink(); + return to_return; +} + +} //namespace. + + diff --git a/nucleus/library/filesystem/byte_filer.h b/nucleus/library/filesystem/byte_filer.h new file mode 100644 index 00000000..0e8bca78 --- /dev/null +++ b/nucleus/library/filesystem/byte_filer.h @@ -0,0 +1,159 @@ +#ifndef BYTE_FILER_CLASS +#define BYTE_FILER_CLASS + +/*****************************************************************************\ +* * +* Name : byte_filer * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace filesystem { + +// forward declarations. +class file_hider; + +//! Provides file managment services using the standard I/O support. + +class byte_filer +{ +public: + byte_filer(); + //!< constructs an object that doesn't access a file yet. + /*!< use open() to make the object valid. */ + + byte_filer(const basis::astring &filename, const basis::astring &permissions); + //!< opens a file "filename" as specified in "permissions". + /*!< these are identical to the standard I/O permissions: + + - "r" - opens text file for reading. + - "w" - opens text file for writing and discards any previous contents. + - "a" - opens text file for writing at end; appends to contents. + - "r+" - opens text file for update (both reading and writing). + - "w+" - creates a text file for update; any previous contents are lost. + - "a+" - opens or creates a text file for update, appending at end. + + a "b" can be added to the end of these to indicate a binary file should + be used instead of a text file. */ + + byte_filer(const char *filename, const char *permissions); + //!< synonym for above but takes char pointers. + + byte_filer(bool auto_close, void *opened); + //!< uses a previously "opened" stdio FILE handle. be careful! + /*!< the "opened" object must be a valid FILE pointer; void * is used to + avoid pulling in the stdio header. this method will not close the file + handle if "auto_close" is false. */ + + ~byte_filer(); + + static size_t file_size_limit(); + //!< returns the maximum size that seek and length can support. + /*!< use the huge_file class if you need to exceed the stdio limits. */ + + bool open(const basis::astring &filename, const basis::astring &permissions); + //!< opens a file with "filename" and "permissions" as in the constructor. + /*!< if a different file had already been opened, it is closed. */ + + void close(); + //!< shuts down the open file, if any. + /*!< open() will have to be invoked before this object can be used again. */ + + basis::astring filename() const; + //!< returns the filename that this was opened with. + + bool good(); + //!< returns true if the file seems to be in the appropriate desired state. + + size_t length(); + //!< returns the file's total length, in bytes. + /*!< this cannot accurately report a file length if it is file_size_limit() + or greater. */ + + size_t tell(); + //!< returns the current position within the file, in terms of bytes. + /*!< this is also limited to file_size_limit(). */ + + void flush(); + //!< forces any pending writes to actually be saved to the file. + + enum origins { + FROM_START, //!< offset is from the beginning of the file. + FROM_END, //!< offset is from the end of the file. + FROM_CURRENT //!< offset is from current cursor position. + }; + + bool seek(int where, origins origin = FROM_START); + //!< places the cursor in the file at "where", based on the "origin". + /*!< note that if the origin is FROM_END, then the offset "where" should + be a negative number if you're trying to access the interior of the file; + positive offsets indicate places after the actual end of the file. */ + + bool eof(); + //!< returns true if the cursor is at (or after) the end of the file. + + int read(basis::abyte *buffer, int buffer_size); + //!< reads "buffer_size" bytes from the file into "buffer". + /*!< for all of the read and write operations, the number of bytes that + is actually processed for the file is returned. */ + int write(const basis::abyte *buffer, int buffer_size); + //!< writes "buffer_size" bytes into the file from "buffer". + + int read(basis::byte_array &buffer, int desired_size); + //!< reads "buffer_size" bytes from the file into "buffer". + int write(const basis::byte_array &buffer); + //!< writes the "buffer" into the file. + + int read(basis::astring &buffer, int desired_size); + //!< read() pulls up to "desired_size" bytes from the file into "buffer". + /*!< since the read() will grab as much data as is available given that it + fits in "desired_size". null characters embedded in the file are a bad + issue here; some other method must be used to read the file instead (such + as the byte_array read above). the "buffer" is shrunk to fit the zero + terminator that we automatically add. */ + + int write(const basis::astring &buffer, bool add_null = false); + //!< stores the string in "buffer" into the file at the current position. + /*!< if "add_null" is true, then write() adds a zero terminator to what + is written into the file. otherwise just the string's non-null contents + are written. */ + + int getline(basis::abyte *buffer, int desired_size); + //!< reads a line of text (terminated by a return) into the "buffer". + int getline(basis::byte_array &buffer, int desired_size); + //!< reads a line of text (terminated by a return) into the "buffer". + int getline(basis::astring &buffer, int desired_size); + //!< reads a line of text (terminated by a return) into the "buffer". + + bool truncate(); + //!< truncates the file after the current position. + + void *file_handle(); + //!< provides a hook to get at the operating system's file handle. + /*!< this is of the type FILE *, as defined by . */ + +private: + file_hider *_handle; //!< the standard I/O support that we rely upon. + basis::astring *_filename; //!< holds onto our current filename. + bool _auto_close; //!< true if the object should close the file. + + // not to be called. + byte_filer(const byte_filer &); + byte_filer &operator =(const byte_filer &); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/directory.cpp b/nucleus/library/filesystem/directory.cpp new file mode 100644 index 00000000..e4e3adcd --- /dev/null +++ b/nucleus/library/filesystem/directory.cpp @@ -0,0 +1,271 @@ +/*****************************************************************************\ +* * +* Name : directory * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "directory.h" +#include "filename.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef __UNIX__ + #include + #include + #include + #include +#endif +#ifdef __WIN32__ + #include +#endif + +/* +#ifdef __WIN32__ + const int MAX_ABS_PATH = 2048; +#elif defined(__APPLE__) + const int MAX_ABS_PATH = 2048; +#else + const int MAX_ABS_PATH = MAX_ABS_PATH; +#endif +*/ + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +using namespace algorithms; +using namespace basis; +using namespace loggers; +using namespace structures; + +namespace filesystem { + +directory::directory(const astring &path, const char *pattern) +: _scanned_okay(false), + _path(new astring), + _files(new string_array), + _folders(new string_array), + _pattern(new astring(pattern)) +{ reset(path, pattern); } + +directory::directory(const directory &to_copy) +: _scanned_okay(false), + _path(new astring), + _files(new string_array), + _folders(new string_array), + _pattern(new astring) +{ reset(*to_copy._path, to_copy._pattern->observe()); } + +directory::~directory() +{ + _scanned_okay = false; + WHACK(_path); + WHACK(_files); + WHACK(_folders); + WHACK(_pattern); +} + +const astring &directory::path() const { return *_path; } + +const astring &directory::pattern() const { return *_pattern; } + +directory &directory::operator =(const directory &to_copy) +{ + if (this == &to_copy) return *this; // oops. + _scanned_okay = false; + reset(*to_copy._path, to_copy._pattern->observe()); + return *this; +} + +astring directory::absolute_path(const astring &rel_path) +{ + char abs_path[MAX_ABS_PATH + 1]; + abs_path[0] = '\0'; +#ifdef __WIN32__ + if (!_fullpath(abs_path, rel_path.s(), MAX_ABS_PATH)) return ""; + return abs_path; +#else + if (!realpath(rel_path.s(), abs_path)) return ""; + return abs_path; +#endif +} + +astring directory::current() +{ + astring to_return("."); // failure result. +#ifdef __WIN32__ + flexichar buffer[MAX_ABS_PATH + 1] = { '\0' }; + GetCurrentDirectory(MAX_ABS_PATH, buffer); + to_return = from_unicode_temp(buffer); +#else + char buffer[MAX_ABS_PATH + 1] = { '\0' }; + if (realpath(".", buffer)) to_return = buffer; +#endif + return to_return; +} + +bool directory::reset(const astring &path, const char *pattern) +{ *_path = path; *_pattern = pattern; return rescan(); } + +bool directory::move_up(const char *pattern) +{ + astring currdir = current(); + return reset(currdir + "/..", pattern); +} + +bool directory::move_down(const astring &subdir, const char *pattern) +{ + astring currdir = current(); + return reset(currdir + "/" + subdir, pattern); +} + +const string_array &directory::files() const { return *_files; } + +const string_array &directory::directories() const { return *_folders; } + +bool directory::rescan() +{ + FUNCDEF("rescan"); + _scanned_okay = false; + _files->reset(); + _folders->reset(); + astring cur_dir = "."; + astring par_dir = ".."; +#ifdef __WIN32__ + // start reading the directory. + WIN32_FIND_DATA wfd; + astring real_path_spec = *_path + "/" + *_pattern; + HANDLE search_handle = FindFirstFile(to_unicode_temp(real_path_spec), &wfd); + if (search_handle == INVALID_HANDLE_VALUE) return false; // bad path. + do { + // ignore the two standard directory entries. + astring filename_transcoded(from_unicode_temp(wfd.cFileName)); + if (!strcmp(filename_transcoded.s(), cur_dir.s())) continue; + if (!strcmp(filename_transcoded.s(), par_dir.s())) continue; + +#ifdef UNICODE +//temp + to_unicode_persist(fudgemart, filename_transcoded); + if (memcmp((wchar_t*)fudgemart, wfd.cFileName, wcslen(wfd.cFileName)*2)) + printf("failed to compare the string before and after transcoding\n"); +#endif + +//wprintf(to_unicode_temp("file is %ls\n"), (wchar_t*)to_unicode_temp(filename_transcoded)); + + filename temp_name(*_path, filename_transcoded.s()); + + // add this to the appropriate list. + if (temp_name.is_directory()) { + _folders->concatenate(filename_transcoded); + } else { + _files->concatenate(filename_transcoded); + +#ifdef UNICODE + to_unicode_persist(fudgemart2, temp_name.raw()); + FILE *fpjunk = _wfopen(fudgemart2, to_unicode_temp("rb")); + if (!fpjunk) + LOG(astring("failed to open the file for testing: ") + temp_name.raw() + "\n"); + if (fpjunk) fclose(fpjunk); +#endif + + } + } while (FindNextFile(search_handle, &wfd)); + FindClose(search_handle); +#endif +#ifdef __UNIX__ + DIR *dir = opendir(_path->s()); +//hmmm: could check errno to determine what caused the problem. + if (!dir) return false; + dirent *entry = readdir(dir); + while (entry) { + char *file = entry->d_name; + bool add_it = true; + if (!strcmp(file, cur_dir.s())) add_it = false; + if (!strcmp(file, par_dir.s())) add_it = false; + // make sure that the filename matches the pattern also. + if (add_it && !fnmatch(_pattern->s(), file, 0)) { + filename temp_name(*_path, file); + // add this to the appropriate list. + if (temp_name.is_directory()) + _folders->concatenate(file); + else + _files->concatenate(file); + } + entry = readdir(dir); + } + closedir(dir); +#endif + shell_sort(_files->access(), _files->length()); + shell_sort(_folders->access(), _folders->length()); + + _scanned_okay = true; + return true; +} + +bool directory::make_directory(const astring &path) +{ +#ifdef __UNIX__ + int mk_ret = mkdir(path.s(), 0777); +#endif +#ifdef __WIN32__ + int mk_ret = mkdir(path.s()); +#endif + return !mk_ret; +} + +bool directory::remove_directory(const astring &path) +{ +#ifdef __UNIX__ + int rm_ret = rmdir(path.s()); +#endif +#ifdef __WIN32__ + int rm_ret = rmdir(path.s()); +#endif + return !rm_ret; +} + +bool directory::recursive_create(const astring &directory_name) +{ +// FUNCDEF("recursive_create"); + filename dir(directory_name); + string_array pieces; + dir.separate(pieces); + for (int i = 0; i < pieces.length(); i++) { + // check each location along the way. + string_array partial = pieces.subarray(0, i); + filename curr; + curr.join(partial); // this is our current location. + // make sure, if we see a drive letter component, that we call it + // a proper directory name. + if (curr.raw()[curr.raw().end()] == ':') + curr = curr.raw() + "/"; + if (curr.exists()) { + if (curr.is_directory()) { + continue; // that's good. + } + return false; // if it's an existing file, we're hosed. + } + // the directory at this place doesn't exist yet. let's create it. + if (!directory::make_directory(curr.raw())) return false; + } + return true; +} + +} // namespace. diff --git a/nucleus/library/filesystem/directory.h b/nucleus/library/filesystem/directory.h new file mode 100644 index 00000000..7012d7be --- /dev/null +++ b/nucleus/library/filesystem/directory.h @@ -0,0 +1,117 @@ +#ifndef DIRECTORY_CLASS +#define DIRECTORY_CLASS + +/*****************************************************************************\ +* * +* Name : directory * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace filesystem { + +//! Implements a scanner that finds all filenames in the directory specified. + +class directory : public virtual basis::root_object +{ +public: + directory(const basis::astring &path, const char *pattern = "*"); + //!< opens up the "path" specified and scans for files and subdirectories. + /*!< if the location was accessible, then the good() method returns true. + note that the "path" should just be a bare directory without any + wildcards attached. the "pattern" can be specified if you wish to + strain out just a subset of the files in the directory. it must meet + the same requirements that the operating system places on wildcard + patterns. */ + + directory(const directory &to_copy); + + virtual ~directory(); + + directory &operator =(const directory &to_copy); + + DEFINE_CLASS_NAME("directory"); + + bool good() const { return _scanned_okay; } + //!< true if the directory existed and its contents were readable. + + const basis::astring &path() const; + //!< returns the directory that we manage. + + const basis::astring &pattern() const; + //!< returns the pattern that the directory class scans for. + + static basis::astring absolute_path(const basis::astring &relative_path); + //!< returns the absolute path to a file with "relative_path". + /*!< an empty string is returned on failure. */ + + bool reset(const basis::astring &path, const char *pattern = "*"); + //!< gets rid of any current files and rescans the directory at "path". + /*!< a new "pattern" can be specified at this time also. */ + + bool move_up(const char *pattern = "*"); + //!< resets the directory to be its own parent. + + bool move_down(const basis::astring &subdir, const char *pattern = "*"); + //!< changes down into a "subdir" of this directory. + /*!< the "subdir" should be just the file name component to change into. + absolute paths will not work. for example, if a directory "/l/jed" has + a subdirectory named "clampett", then use: @code + my_dir->move_down("clampett") @endcode + */ + + bool rescan(); + //!< reads our current directory's contents over again. + + const structures::string_array &files() const; + //!< returns the list of files that we found in this directory. + /*!< these are all assumed to be located in our given path. to find out + more information about the files themselves, construct a filename object + with the path() and the file of interest. */ + + const structures::string_array &directories() const; + //!< these are the directory names from the folder. + /*!< they can also be examined using the filename object. note that + this does not include the entry for the current directory (.) or the + parent (..). */ + + // static methods of general directory-related interest. + + static basis::astring current(); + //!< returns the current directory, as reported by the operating system. + + static bool make_directory(const basis::astring &path); + //!< returns true if the directory "path" could be created. + + static bool remove_directory(const basis::astring &path); + //!< returns true if the directory "path" could be removed. + + static bool recursive_create(const basis::astring &directory_name); + //!< returns true if the "directory_name" can be created or already exists. + /*!< false returns indicate that the operating system wouldn't let us + make the directory, or that we didn't have sufficient permissions to + access an existing directory to view it or move into it. */ + +private: + bool _scanned_okay; //!< did this directory work out? + basis::astring *_path; //!< the directory we're looking at. + structures::string_array *_files; //!< the list of files. + structures::string_array *_folders; //!< the list of directories. + basis::astring *_pattern; //!< the pattern used to find the files. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/directory_tree.cpp b/nucleus/library/filesystem/directory_tree.cpp new file mode 100644 index 00000000..77ea9472 --- /dev/null +++ b/nucleus/library/filesystem/directory_tree.cpp @@ -0,0 +1,948 @@ +/*****************************************************************************\ +* * +* Name : directory_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "directory.h" +#include "directory_tree.h" +#include "filename.h" +#include "filename_list.h" +#include "filename_tree.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace basis; +using namespace structures; +using namespace nodes; +using namespace textual; + +//#define DEBUG_DIRECTORY_TREE + // uncomment for noisier version. + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) +//CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +////////////// + +namespace filesystem { + +class dir_tree_iterator : public filename_tree::iterator +{ +public: + filename_tree *_current; + + dir_tree_iterator(const filename_tree *initial, + tree::traversal_directions dir) + : filename_tree::iterator(initial, dir), _current(NIL) {} +}; + +////////////// + +directory_tree::directory_tree() +: _scanned_okay(false), + _path(new astring), + _pattern(new astring), + _real_tree(new filename_tree), + _ignore_files(false), + _creator(new fname_tree_creator) +{ +} + +directory_tree::directory_tree(const astring &path, const char *pattern, + bool ignore_files) +: _scanned_okay(false), + _path(new astring(path)), + _pattern(new astring(pattern)), + _real_tree(NIL), + _ignore_files(ignore_files), + _creator(new fname_tree_creator) +{ + reset(path, pattern); +} + +directory_tree::~directory_tree() +{ + _scanned_okay = false; + WHACK(_path); + WHACK(_pattern); + WHACK(_real_tree); + WHACK(_creator); +} + +const astring &directory_tree::path() const { return *_path; } + +int directory_tree::packed_size() const +{ + return 2 * PACKED_SIZE_INT32 + + _path->packed_size() + + _pattern->packed_size() + + _real_tree->recursive_packed_size(); +} + +void directory_tree::pack(byte_array &packed_form) const +{ + attach(packed_form, int(_scanned_okay)); + attach(packed_form, int(_ignore_files)); + _path->pack(packed_form); + _pattern->pack(packed_form); + _real_tree->recursive_pack(packed_form); +} + +bool directory_tree::unpack(byte_array &packed_form) +{ + int temp; + if (!detach(packed_form, temp)) return false; + _scanned_okay = temp; + if (!detach(packed_form, temp)) return false; + _ignore_files = temp; + if (!_path->unpack(packed_form)) return false; + if (!_pattern->unpack(packed_form)) return false; + WHACK(_real_tree); + _real_tree = (filename_tree *)packable_tree::recursive_unpack + (packed_form, *_creator); + if (!_real_tree) { + _real_tree = new filename_tree; // reset it. + return false; + } + return true; +} + +void directory_tree::text_form(astring &target, bool show_files) +{ + dir_tree_iterator *ted = start(directory_tree::prefix); + // create our iterator to do a prefix traversal. + + int depth; // current depth in tree. + filename curr; // the current path the iterator is at. + string_array files; // the filenames held at the iterator. + + while (current(*ted, curr, files)) { + // we have a good directory to show. + directory_tree::depth(*ted, depth); + target += string_manipulation::indentation(depth * 2) + astring("[") + + curr.raw() + "]" + parser_bits::platform_eol_to_chars(); + if (show_files) { + astring names; + for (int i = 0; i < files.length(); i++) names += files[i] + " "; + if (names.length()) { + astring split; + string_manipulation::split_lines(names, split, depth * 2 + 2); + target += split + parser_bits::platform_eol_to_chars(); + } + } + + // go to the next place. + next(*ted); + } + + throw_out(ted); +} + +void directory_tree::traverse(const astring &path, const char *pattern, + filename_tree &add_to) +{ +#ifdef DEBUG_DIRECTORY_TREE + FUNCDEF("traverse"); +#endif + // prepare the current node. + add_to._dirname = filename(path, astring::empty_string()); + add_to._files.reset(); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("working on node ") + add_to._dirname.raw()); +#endif + + // open the directory. + directory curr(path, "*"); + if (!curr.good()) return; + + if (!_ignore_files) { + // add all the files to the current node. + directory curr_stringent(path, pattern); + add_to._files = curr_stringent.files(); + } + + // now iterate across the directories here and add a sub-node for each one, + // and recursively traverse that sub-node also. + const string_array &dirs = curr.directories(); + for (int i = 0; i < dirs.length(); i++) { + filename_tree *new_branch = NIL; + astring new_path = path + filename::default_separator() + dirs[i]; +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("seeking path: ") + new_path); +#endif + for (int q = 0; q < add_to.branches(); q++) { + filename_tree *curr_kid = (filename_tree *)add_to.branch(q); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("curr kid: ") + curr_kid->_dirname); +#endif + if (filename(new_path).raw().iequals(filename + (curr_kid->_dirname).raw())) { + new_branch = curr_kid; +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("using existing branch for ") + new_path); +#endif + break; + } + } + if (!new_branch) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("adding new branch for ") + new_path); +#endif + new_branch = new filename_tree; + add_to.attach(new_branch); + new_branch->_depth = add_to._depth + 1; + } +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("traversing sub-node ") + new_path); +#endif + traverse(new_path, pattern, *new_branch); + } +} + +bool directory_tree::reset(const astring &path, const char *pattern) +{ + _scanned_okay = false; + WHACK(_real_tree); + *_path = path; + *_pattern = pattern; + _real_tree = new filename_tree; + + // check that the top-level is healthy. + directory curr(path, "*"); + if (!curr.good()) return false; + // our only exit condition; other directories might not be accessible + // underneath, but the top one must be accessible for us to even start + // the scanning. + + traverse(path, pattern, *_real_tree); + _scanned_okay = true;; + return true; +} + +dir_tree_iterator *directory_tree::start_at(filename_tree *start, + traversal_types type) const +{ + // translate to the lower level traversal enum. + tree::traversal_directions dir = tree::prefix; + if (type == infix) dir = tree::infix; + else if (type == postfix) dir = tree::postfix; + + return new dir_tree_iterator(start, dir); +} + +dir_tree_iterator *directory_tree::start(traversal_types type) const +{ + // translate to the lower level traversal enum. + tree::traversal_directions dir = tree::prefix; + if (type == infix) dir = tree::infix; + else if (type == postfix) dir = tree::postfix; + + return new dir_tree_iterator(_real_tree, dir); +} + +bool directory_tree::jump_to(dir_tree_iterator &scanning, + const astring &sub_path) +{ +#ifdef DEBUG_DIRECTORY_TREE + FUNCDEF("jump_to"); +#endif + string_array pieces; + filename(sub_path).separate(pieces); + for (int i = 0; i < pieces.length(); i++) { + filename_tree *curr = dynamic_cast(scanning.current()); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("at ") + curr->_dirname.raw()); +#endif + string_array sub_pieces = pieces.subarray(i, i); + filename curr_path; + curr_path.join(sub_pieces); + curr_path = filename(curr->_dirname.raw() + filename::default_separator() + + curr_path.raw()); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("made curr path ") + curr_path.raw()); +#endif + if (!curr) return false; + bool found_it = false; + for (int j = 0; j < curr->branches(); j++) { + filename_tree *sub = dynamic_cast(curr->branch(j)); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("looking at ") + sub->_dirname.raw()); +#endif + if (sub->_dirname.compare_prefix(curr_path)) { + // this part matches! + scanning.push(sub); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("found at ") + sub->_dirname.raw()); +#endif + found_it = true; + break; + } + } + if (!found_it) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("could not find ") + curr_path.raw()); +#endif + return false; + } + } + return true; +} + +filename_tree *directory_tree::goto_current(dir_tree_iterator &scanning) +{ + if (!scanning._current) { + // this one hasn't been advanced yet, or it's already over with. + scanning._current = (filename_tree *)scanning.next(); + } + // now check that we're healthy. + if (!scanning._current) return NIL; // all done. + + // cast the tree to the right type. + return dynamic_cast(scanning._current); +} + +bool directory_tree::current_dir(dir_tree_iterator &scanning, + filename &dir_name) +{ + dir_name = astring::empty_string(); + filename_tree *tof = goto_current(scanning); + if (!tof) return false; + dir_name = tof->_dirname; + return true; +} + +bool directory_tree::current(dir_tree_iterator &scanning, + filename &dir_name, string_array &to_fill) +{ + // clear any existing junk. + dir_name = astring::empty_string(); + to_fill.reset(); + + filename_tree *tof = goto_current(scanning); + if (!tof) return false; + + // fill in what they wanted. + dir_name = tof->_dirname; + tof->_files.fill(to_fill); + + return true; +} + +bool directory_tree::current(dir_tree_iterator &scanning, + filename &dir_name, filename_list &to_fill) +{ + // clear any existing junk. + dir_name = astring::empty_string(); + to_fill.reset(); + + filename_tree *tof = goto_current(scanning); + if (!tof) return false; + + // fill in what they wanted. + dir_name = tof->_dirname; + to_fill = tof->_files; + + return true; +} + +filename_list *directory_tree::access(dir_tree_iterator &scanning) +{ + filename_tree *tof = goto_current(scanning); + if (!tof) return NIL; + return &tof->_files; +} + +bool directory_tree::depth(dir_tree_iterator &scanning, int &depth) +{ + depth = -1; // invalid as default. + filename_tree *tof = goto_current(scanning); + if (!tof) return false; + depth = tof->_depth; + return true; +} + +bool directory_tree::children(dir_tree_iterator &scanning, int &kids) +{ + kids = -1; // invalid as default. + filename_tree *tof = goto_current(scanning); + if (!tof) return false; + kids = tof->branches(); + return true; +} + +bool directory_tree::next(dir_tree_iterator &scanning) +{ + scanning._current = (filename_tree *)scanning.next(); + return !!scanning._current; +} + +void directory_tree::throw_out(dir_tree_iterator * &to_whack) +{ + WHACK(to_whack); +} + +filename_tree *directory_tree::seek(const astring &dir_name_in, + bool ignore_initial) const +{ + FUNCDEF("seek"); + array examining; + // the list of nodes we're currently looking at. + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("seeking on root of: ") + *_path); +#endif + + astring dir_name = filename(dir_name_in).raw(); + // set the search path up to have the proper prefix. + if (ignore_initial) + dir_name = path() + filename::default_separator() + + filename(dir_name_in).raw(); + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("adding root: ") + _real_tree->_dirname); +#endif + examining += _real_tree; + + astring sequel; // holds extra pieces from filename comparisons. + + // chew on the list of nodes to examine until we run out. + while (examining.length()) { + int posn; + bool found = false; + // start looking at all the items in the list, even though we might have + // to abandon the iteration if we find a match. + filename_tree *check = NIL; + for (posn = 0; posn < examining.length(); posn++) { + check = examining[posn]; + filename current(check->_dirname); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("looking at ") + current.raw()); +#endif + if (current.compare_prefix(dir_name, sequel)) { + // we have a match! +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("matched! at ") + current.raw()); +#endif + found = true; + if (!sequel) { + // whoa! an exact match. we're done now. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("exact match at ") + current.raw() + "! done!!!"); +#endif + return check; + } else { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("inexact match because sequel=") + sequel); +#endif + } + break; + } + } + if (!found) return NIL; // we found nothing comparable. + + // we found a partial match. that means we should start looking at this + // node's children for the exact match. + if (!check) { + // this is a serious logical error! + LOG("serious logical error: tree was not located."); + return NIL; + } + examining.reset(); // clear the existing nodes. + for (int i = 0; i < check->branches(); i++) + examining += (filename_tree *)check->branch(i); + } + + return NIL; // we found nothing that looked like that node. +} + +bool directory_tree::calculate(bool just_size) +{ return calculate(_real_tree, just_size); } + +bool directory_tree::calculate(filename_tree *start, bool just_size) +{ + FUNCDEF("calculate"); + dir_tree_iterator *ted = start_at(start, directory_tree::postfix); + // create our iterator to do a postfix traversal. why postfix? well, + // prefix has been used elsewhere and since it doesn't really matter what + // order we visit the nodes here, it's good to change up. + + int depth; // current depth in tree. + filename curr; // the current path the iterator is at. + filename_list *files; // the filenames held at the iterator. + + while (directory_tree::current_dir(*ted, curr)) { + // we have a good directory to show. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("calcing node ") + curr.raw()); +#endif + files = directory_tree::access(*ted); + directory_tree::depth(*ted, depth); + for (int i = 0; i < files->elements(); i++) { + if (!files->borrow(i)->calculate(curr.raw(), just_size)) { + LOG(astring("failure to calculate ") + files->get(i)->text_form()); + } + } + + directory_tree::next(*ted); + } + + directory_tree::throw_out(ted); + return true; +} + +bool directory_tree::compare_trees(const directory_tree &source, + const directory_tree &target, filename_list &differences, + file_info::file_similarity how_to_compare) +{ + return compare_trees(source, astring::empty_string(), target, + astring::empty_string(), differences, how_to_compare); +} + +bool directory_tree::compare_trees(const directory_tree &source, + const astring &source_start_in, const directory_tree &target, + const astring &target_start_in, filename_list &differences, + file_info::file_similarity how_compare) +{ + FUNCDEF("compare_trees"); + differences.reset(); // prepare it for storage. + + // make sure we get canonical names to work with. + filename source_start(source_start_in); + filename target_start(target_start_in); + + dir_tree_iterator *ted = source.start(directory_tree::prefix); + // create our iterator to do a prefix traversal. + + astring real_source_start = source.path(); + if (source_start.raw().t()) { + // move to the right place. + real_source_start = real_source_start + filename::default_separator() + + source_start.raw(); + if (!directory_tree::jump_to(*ted, source_start.raw())) { + // can't even start comparing. + LOG(astring("failed to find source start in tree, given as ") + + source_start.raw()); + return false; + } + } + + filename curr; // the current path the iterator is at. + filename_list files; // the filenames held at the iterator. + + // calculate where our comparison point is on the source. + int source_pieces = 0; + { + string_array temp; + filename(real_source_start).separate(temp); + source_pieces = temp.length(); + } + + bool seen_zero_pieces = false; + while (directory_tree::current(*ted, curr, files)) { + // we're in a place in the source tree now. let's compare it with the + // target's recollection. + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("curr dir in tree: ") + curr.raw()); +#endif + + string_array pieces; + curr.separate(pieces); // get the components of the current location. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("name in pieces:") + pieces.text_form()); +#endif + pieces.zap(0, source_pieces - 1); + // snap the root components out of there. + + filename corresponding_name; + corresponding_name.join(pieces); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("computed target name as: ") + corresponding_name); +#endif + filename original_correspondence(corresponding_name); + + if (!corresponding_name.raw().t()) { + if (seen_zero_pieces) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("breaking out now due to empty correspondence")); +#endif + break; + } + seen_zero_pieces = true; + } + if (target_start.raw().t()) { + corresponding_name = filename(target_start.raw() + + filename::default_separator() + corresponding_name.raw()); + } +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("target with start is: ") + corresponding_name); +#endif + + filename_tree *target_now = target.seek(corresponding_name.raw(), true); + if (!target_now) { + // that entire sub-tree is missing. add all of the files here into + // the list. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("could not find dir in target for ") + curr.raw() + + " which we computed corresp as " + corresponding_name.raw()); +#endif + } + + // now scan across all the files that are in our source list. + for (int i = 0; i < files.elements(); i++) { + if (!target_now // there was no node, so we're adding everything... + || !target_now->_files.member_with_state(*files[i], how_compare) ) { + // ... or we need to add this file since it's missing. + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("adding record: ") + files[i]->text_form()); +#endif + + file_info *new_record = new file_info(*files[i]); + // record the file time for use later in saving. + new_record->calculate(curr, true); + astring original = new_record->raw(); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("current: ") + new_record->raw()); +#endif + + astring actual_name = source_start.raw(); +#ifdef DEBUG_DIRECTORY_TREE + if (actual_name.t()) LOG(astring("sname=") + actual_name); +#endif + if (actual_name.length()) actual_name += filename::default_separator(); + actual_name += original_correspondence.raw(); + if (actual_name.length()) actual_name += filename::default_separator(); + actual_name += new_record->raw(); +#ifdef DEBUG_DIRECTORY_TREE + if (actual_name.t()) LOG(astring("sname=") + actual_name); +#endif + (filename &)(*new_record) = filename(actual_name); + + astring targ_name = corresponding_name.raw(); +#ifdef DEBUG_DIRECTORY_TREE + if (targ_name.t()) LOG(astring("tname=") + targ_name); +#endif + if (targ_name.length()) targ_name += filename::default_separator(); + targ_name += original; +#ifdef DEBUG_DIRECTORY_TREE + if (targ_name.t()) LOG(astring("tname=") + targ_name); +#endif + + new_record->secondary(targ_name); + + differences += new_record; +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("came out as: ") + new_record->text_form()); +#endif + } + } + + // go to the next place. + directory_tree::next(*ted); + } + + directory_tree::throw_out(ted); + + return true; +} + +outcome directory_tree::find_common_root(const astring &finding, bool exists, + filename_tree * &found, astring &reassembled, string_array &pieces, + int &match_place) +{ +#ifdef DEBUG_DIRECTORY_TREE + FUNCDEF("find_common_root"); +#endif + // test the path to find what it is. + filename adding(finding); + if (exists && !adding.good()) + return common::BAD_INPUT; // not a good path. + int file_subtract = 0; // if it's a file, then we remove last component. + if (exists && !adding.is_directory()) file_subtract = 1; + + // break up the path into pieces. + pieces.reset(); + adding.separate(pieces); + + // break up our root into pieces; we must take off components that are + // already in the root. + string_array root_pieces; + filename temp_file(path()); + temp_file.separate(root_pieces); + + // locate the last place where the path we were given touches our tree. + // it could be totally new, partially new, or already contained. + filename_tree *last_match = _real_tree; // where the common root is located. + int list_length = pieces.length() - file_subtract; + reassembled = ""; + + // we must put all the pieces in that already come from the root. + for (int i = 0; i < root_pieces.length() - 1; i++) { + bool add_slash = false; + if (reassembled.length() && (reassembled[reassembled.end()] != '/') ) + add_slash = true; + if (add_slash) reassembled += "/"; + reassembled += pieces[i]; + if (reassembled[reassembled.end()] == ':') { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("skipping drive component ") + reassembled); +#endif + continue; + } + } + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("after pre-assembly, path is ") + reassembled); +#endif + + outcome to_return = common::NOT_FOUND; + + for (match_place = root_pieces.length() - 1; match_place < list_length; + match_place++) { + // add a slash if there's not one present already. + bool add_slash = false; + if (reassembled.length() && (reassembled[reassembled.end()] != '/') ) + add_slash = true; + // add the next component in to our path. + if (add_slash) reassembled += "/"; + reassembled += pieces[match_place]; + // special case for dos paths. + if (reassembled[reassembled.end()] == ':') { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("skipping drive component ") + reassembled); +#endif + continue; + } + reassembled = filename(reassembled).raw(); // force compliance with OS. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("now seeking ") + reassembled); +#endif + filename_tree *sought = seek(reassembled, false); + if (!sought) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("couldn't find ") + reassembled); +#endif + if (!exists && (match_place == list_length - 1)) { + // see if we can get a match on a file rather than a directory, but + // only if we're near the end of the compare. + if (last_match->_files.member(pieces[match_place])) { + // aha! a file match. + to_return = common::OKAY; + match_place--; + break; + } + } + match_place--; + break; + } else { + // record where we last had some success. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("found subtree for ") + reassembled); +#endif + last_match = sought; + } + } + // this is a success, but our loop structure can put us one past the right + // place. + if (match_place >= list_length) { + match_place = list_length - 1; + to_return = common::OKAY; + } + + found = last_match; + return to_return; +} + +outcome directory_tree::add_path(const astring &new_item, bool just_size) +{ + FUNCDEF("add_path"); + // test the path to find out what it is. + filename adding(new_item); + if (!adding.good()) { + LOG(astring("non-existent new item! ") + new_item); + return common::BAD_INPUT; // not a good path. + } + int file_subtract = 0; // if it's a file, then we remove last component. + if (!adding.is_directory()) file_subtract = 1; +#ifdef DEBUG_DIRECTORY_TREE + if (file_subtract) LOG(astring("adding a file ") + new_item) + else LOG(astring("adding a directory ") + new_item); +#endif + + // find the common root, break up the path into pieces, and tell us where + // we matched. + string_array pieces; + filename_tree *last_match = NIL; + int comp_index; + astring reassembled; // this will hold the common root. + outcome ret = find_common_root(new_item, true, last_match, reassembled, + pieces, comp_index); + if (!last_match) { + LOG(astring("serious error finding common root for ") + new_item + + ", got NIL tree."); + return common::FAILURE; // something serious isn't right. + } + + if (!file_subtract) { + if (ret != common::OKAY) { + // if it's a new directory, we add a new node for traverse to work on. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("now adding node for ") + reassembled); +#endif + filename_tree *new_branch = new filename_tree; + new_branch->_depth = last_match->_depth + 1; + last_match->attach(new_branch); + last_match = new_branch; + } else { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("matched properly. reassembled set to ") + reassembled); +#endif + } + } + + if (file_subtract) { + if (ret != common::OKAY) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("common gave us posn of: ") + reassembled); +#endif + // handle the case for files now that we have our proper node. + string_array partial_pieces; + filename(reassembled).separate(partial_pieces); + int levels_missing = pieces.length() - partial_pieces.length(); + + // we loop over all the pieces that were missing in between the last + // common root and the file's final location. + for (int i = 0; i < levels_missing; i++) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("adding intermediate directory: ") + reassembled); +#endif + filename_tree *new_branch = new filename_tree; + new_branch->_depth = last_match->_depth + 1; + new_branch->_dirname = filename(reassembled).raw(); + last_match->attach(new_branch); + last_match = new_branch; + reassembled += astring("/") + pieces[partial_pieces.length() + i]; + reassembled = filename(reassembled).raw(); // canonicalize. + } + } + + if (!last_match->_files.find(pieces[pieces.last()])) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("adding new file ") + pieces[pieces.last()] + + " at " + reassembled); +#endif + file_info *to_add = new file_info(pieces[pieces.last()], 0); + to_add->calculate(reassembled, just_size); + last_match->_files += to_add; + } else { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("not adding existing file ") + pieces[pieces.last()] + + " at " + reassembled); +#endif + } + } else { + // handle the case for directories. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("doing traverse in ") + last_match->_dirname + + " to add " + reassembled); +#endif + traverse(reassembled, "*", *last_match); +//hmmm: maybe provide pattern capability instead of assuming all files. + calculate(last_match, just_size); + } + + return common::OKAY; +} + +outcome directory_tree::remove_path(const astring &zap_item) +{ +#ifdef DEBUG_DIRECTORY_TREE + FUNCDEF("remove_path"); +#endif + // find the common root, if one exists. if not, we're not going to do this. + string_array pieces; + filename_tree *last_match = NIL; + int comp_index; + astring reassembled; + outcome ret = find_common_root(zap_item, false, last_match, reassembled, + pieces, comp_index); + if (!last_match) return common::NOT_FOUND; + // if we didn't actually finish iterating to the file, then we're not + // whacking anything. + if (ret != common::OKAY) { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("got error seeking ") + zap_item + " of " + + common::outcome_name(ret)); +#endif + return ret; + } + + if (comp_index == pieces.last()) { + // if the names match fully, then we're talking about a directory. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("found directory match for ") + zap_item); +#endif + } else { +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("may have found file match for ") + zap_item); +#endif + filename to_seek(pieces[pieces.last()]); + if (!last_match->_files.member(to_seek)) { + // this file is not a member, so we must say it's not found. +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("couldn't find file match in common root for ") + zap_item); +#endif + return common::NOT_FOUND; + } else { + int indy = last_match->_files.locate(to_seek); +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("found match to remove for ") + zap_item); +#endif + last_match->_files.zap(indy, indy); + return common::OKAY; // done! + } + } + +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("going to whack node at: ") + last_match->_dirname.raw()); +#endif + + // we're whacking directories, so we need to take out last_match and below. + filename_tree *parent = (filename_tree *)last_match->parent(); + if (!parent || (last_match == _real_tree)) { + // this seems to be matching the whole tree. we disallow that. +#ifdef DEBUG_DIRECTORY_TREE + LOG("there's a problem whacking this node; it's the root."); +#endif + return common::BAD_INPUT; + } +#ifdef DEBUG_DIRECTORY_TREE + LOG(astring("pruning tree at ") + last_match->_dirname.raw()); +#endif + parent->prune(last_match); + WHACK(last_match); + + return common::OKAY; +} + +} //namespace. + + diff --git a/nucleus/library/filesystem/directory_tree.h b/nucleus/library/filesystem/directory_tree.h new file mode 100644 index 00000000..9c89ba43 --- /dev/null +++ b/nucleus/library/filesystem/directory_tree.h @@ -0,0 +1,224 @@ +#ifndef DIRECTORY_TREE_CLASS +#define DIRECTORY_TREE_CLASS + +/*****************************************************************************\ +* * +* Name : directory_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "directory.h" +#include "file_info.h" + +#include +#include +#include +#include + +namespace filesystem { + +// forward declarations. +class dir_tree_iterator; +class filename; +class filename_list; +class filename_tree; +class fname_tree_creator; + +//! An object that traverses directory trees and provides a view of all files. + +class directory_tree : public virtual basis::packable +{ +public: + directory_tree(); //!< constructs an empty tree. + + directory_tree(const basis::astring &path, const char *pattern = "*", + bool ignore_files = false); + //!< opens up the "path" specified and scans for files and subdirectories. + /*!< if the location was accessible, then the good() method returns true. + note that the "path" should just be a bare directory without any + wildcards attached. the "pattern" can be specified if you wish to + strain out just a subset of the files in the directory. note that + unlike the directory object, directory_tree applies the wildcard to + filenames only--all sub-directories are included. the pattern must meet + the same requirements that the operating system places on wildcard + patterns. if "ignore_files" is true, then no files are considered and + only the tree of directories is gathered. */ + + ~directory_tree(); + + DEFINE_CLASS_NAME("directory_tree"); + + bool good() const { return _scanned_okay; } + //!< returns true if the directory existed and we read its contents. + + const basis::astring &path() const; + //!< returns the root of the directory tree that we manage. + + bool reset(const basis::astring &path, const char *pattern = "*"); + //!< gets rid of any current files and rescans the directory at "path". + /*!< a new "pattern" can be specified at this time also. true is returned + if the process was started successfully at "path"; there might be + problems with subdirectories, but at least the "path" got validated. */ + + filename_tree *seek(const basis::astring &dir_name, bool ignore_initial) const; + //!< finds the "dir_name" in our tree. + /*!< locates the node that corresponds to the directory name contained in + "dir_name" and returns the filename_tree rooted at that node. if the + "ignore_initial" flag is true, then dir_name is expected to omit the + path() where "this" tree is rooted. */ + + virtual int packed_size() const; + //!< reports the size after packing up the tree. + virtual void pack(basis::byte_array &packed_form) const; + //!< packs the directory_tree into a byte_array. + virtual bool unpack(basis::byte_array &packed_form); + //!< unpacks the directory_tree from a byte_array. + + bool calculate(bool just_size); + //!< visits each file in the directory_tree and calculates its attributes. + /*!< the attributes include file size and checksum. if "just_size" is + true, then no checksum is computed. */ + + bool calculate(filename_tree *start, bool just_size); + //!< a calculate method that starts at a specific node rather than the root. + + basis::outcome add_path(const basis::astring &new_item, bool just_size = false); + //!< adds a "new_item" into the tree. + /*!< this is useful when one knows that new files exist under the + directory, but one doesn't want to recalculate the entire tree. the new + item will automatically be calculated. the item can be either a file or + directory that's under the root. the root directory name should not be + included in the "new_item". */ + + basis::outcome remove_path(const basis::astring &zap_item); + //!< removes the "zap_item" from the tree. + /*!< this only works for cases where one knows that an item has been + removed in the filesystem. if the item is still really there, then the + next rescan will put it back into the tree. */ + + static bool compare_trees(const directory_tree &source, + const directory_tree &target, filename_list &differences, + file_info::file_similarity how_to_compare); + //!< compares the tree in "source" with the tree in "target". + /*!< the two root names may be different, but everything below the root + in "source" will be checked against "target". the "differences" between + the two trees will be compiled. note that this does not perform any disk + access; it merely compares the two trees' current contents. + the "differences" list's members will have a primary filename set to + the source path and an alternate filename set to the location in the + target. the "how_to_compare" value will dictate what aspects of file + equality are used. */ + + static bool compare_trees(const directory_tree &source, + const basis::astring &source_start, const directory_tree &target, + const basis::astring &target_start, filename_list &differences, + file_info::file_similarity how_to_compare); + // compares the trees but not at their roots. the location on the source + // side is specified by "source_start", which must be a path found under + // the "source" tree. similarly, the "target_start" will be the location + // compared with the "source" + "source_start". the "diffs" will still + // be valid with respect to "source" rather than "source_start". + + void text_form(basis::astring &tree_dump, bool show_files = true); + //!< provides a visual representation of the tree in "tree_dump". + /*!< if "show_files" is not true, then only the directories will be + shown. */ + + // Note on the iterator functions: the iterator becomes invalid if the + // directory tree is reset. the only valid operation on the iterator + // at that point is to call throw_out(). + + enum traversal_types { + prefix, //!< prefix means that subnodes are processed after their parent. + infix, //!< infix (for binary trees) goes 1) left, 2) current, 3) right. + postfix //!< postfix means that subnodes are traversed first (depth first). + }; + + dir_tree_iterator *start(traversal_types type) const; + //!< starts an iterator on the directory tree. + + dir_tree_iterator *start_at(filename_tree *start, + traversal_types type) const; + //!< starts the iterator at a specific "start" node. + + static bool jump_to(dir_tree_iterator &scanning, const basis::astring &sub_path); + //!< seeks to a "sub_path" below the iterator's current position. + /*!< tries to take the iterator "scanning" down to a "sub_path" that is + underneath its current position. true is returned on success. */ + + static bool current_dir(dir_tree_iterator &scanning, filename &dir_name); + //!< sets "dir_name" to the directory name at the "scanning" location. + + static bool current(dir_tree_iterator &scanning, filename &dir_name, + structures::string_array &to_fill); + //!< retrieves the information for the iterator's current location. + /*!< fills the "to_fill" array with filenames that are found at the + "scanning" iterator's current position in the tree. the "dir_name" + for that location is also set. if the iterator has ended, then false + is returned. */ + + static bool current(dir_tree_iterator &scanning, filename &dir_name, + filename_list &to_fill); + //!< similar to the above but provides a list of the real underlying type. + + static filename_list *access(dir_tree_iterator &scanning); + //!< more dangerous operation that lets the actual list be manipulated. + /*!< NIL is returned if there was a problem accessing the tree + at the iterator position. */ + + static bool depth(dir_tree_iterator &scanning, int &depth); + //!< returns the current depth of the iterator. + /*!< a depth of zero means the iterator is at the root node for the tree. */ + + static bool children(dir_tree_iterator &scanning, int &children); + //!< returns the number of children for the current node. + + static bool next(dir_tree_iterator &scanning); + //!< goes to the next filename in the "scanning" iterator. + /*!< true is returned if there is an entry there. */ + + static void throw_out(dir_tree_iterator * &to_whack); + //!< cleans up an iterator that was previously opened with start(). + +private: + bool _scanned_okay; //!< did this directory work out? + basis::astring *_path; //!< the directory we're looking at. + basis::astring *_pattern; //!< the pattern used to find the files. + filename_tree *_real_tree; //!< the tree of directory contents we build. + bool _ignore_files; //!< true if they don't care about the files. + fname_tree_creator *_creator; //!< creates blank trees during unpacking. + + static filename_tree *goto_current(dir_tree_iterator &scanning); + //!< goes to the current node for "scanning" and returns the tree there. + /*!< if there are no nodes left, NIL is returned. */ + + void traverse(const basis::astring &path, const char *pattern, + filename_tree &add_to); + //!< recursively adds a "path" given the filename "pattern". + /*!< assuming that we want to add the files at "path" using the "pattern" + into the current node "add_to", we will also scoot down all sub-dirs + and recursively invoke traverse() to add those also. */ + + basis::outcome find_common_root(const basis::astring &path, bool exists, + filename_tree * &common_root, basis::astring &common_path, + structures::string_array &pieces, int &match_place); + //!< locates the node where this tree and "path" have membership in common. + /*!< if "exists" is true, then the "path" is tested for existence and + otherwise it's assumed that the path no longer exists. the "common_root" + is the last node that's in both places, the "common_path" is the name of + that location, the list of "pieces" is "path" broken into its components, + and the "match_place" is the index in "pieces" of the common node. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/file_info.cpp b/nucleus/library/filesystem/file_info.cpp new file mode 100644 index 00000000..ed66d7b4 --- /dev/null +++ b/nucleus/library/filesystem/file_info.cpp @@ -0,0 +1,230 @@ +/*****************************************************************************\ +* * +* Name : file_info * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "file_info.h" +#include "huge_file.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define DEBUG_FILE_INFO + // uncomment for noisy version. + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +using namespace basis; +using namespace structures; + +namespace filesystem { + +file_info::file_info() +: filename(astring::empty_string()), + _file_size(0), + _time(), + _checksum(), + c_secondary(), + c_attachment() +{} + +file_info::file_info(const filename &to_copy, double file_size, + const file_time &time, int checksum) +: filename(to_copy), + _file_size(file_size), + _time(time), + _checksum(checksum), + c_secondary(), + c_attachment() +{} + +file_info::file_info(const file_info &to_copy) +: filename(to_copy), + _file_size(to_copy._file_size), + _time(to_copy._time), + _checksum(to_copy._checksum), + c_secondary(to_copy.c_secondary), + c_attachment(to_copy.c_attachment) +{ +} + +file_info::~file_info() {} + +const byte_array &file_info::attachment() const { return c_attachment; } + +void file_info::attachment(const byte_array &new_attachment) +{ c_attachment = new_attachment; } + +const astring &file_info::secondary() const { return c_secondary; } + +void file_info::secondary(const astring &new_sec) { c_secondary = new_sec; } + +astring file_info::text_form() const +{ + astring to_return = raw() + + a_sprintf(", size=%0.f, chksum=%d", _file_size, _checksum); + if (c_secondary.t()) + to_return += astring(", 2ndary=") + c_secondary; + return to_return; +} + +bool file_info::calculate(const astring &prefix, bool just_size, int checksum_edge) +{ + FUNCDEF("calculate"); + filename full; + if (prefix.t()) full = prefix + "/" + *this; + else full = *this; + if (!full.exists()) { +#ifdef DEBUG_FILE_INFO + LOG(astring("failed to find file: ") + full.raw()); +#endif + return false; + } + // get time again. + _time = file_time(full); + +//#ifdef DEBUG_FILE_INFO +// astring temptext; +// _time.text_form(temptext); +// LOG(astring("file calculate on ") + full.raw() + " time=" + temptext); +//#endif + + // open the file for reading. + huge_file to_read(full.raw(), "rb"); + if (!to_read.good()) { +#ifdef DEBUG_FILE_INFO + LOG(astring("file has non-good status: ") + full.raw()); +#endif + return false; // why did that happen? + } + // set the size appropriately. + _file_size = to_read.length(); + if (just_size) + return true; // done for that case. + + // now read the file and compute a checksum. + uint16 curr_sum = 0; // the current checksum being computed. + byte_array chunk; // temporary chunk of data from file. + +//hmmm: make this optimization (hack) optional! + + // this algorithm takes a chunk on each end of the file for checksums. + // this saves us from reading a huge amount of data, although it will be + // fooled if a huge binary file is changed only in the middle and has the + // same size as before. for most purposes, this is not a problem, although + // databases that are fixed size might fool us. if records are written in + // the middle without updating the head or tail sections, then we're hosed. + + bool skip_tail = false; // true if we don't need the tail piece. + double head_start = 0, head_end = 0, tail_start = 0, + tail_end = _file_size - 1; + if (_file_size <= double(2 * checksum_edge)) { + // we're applying a rule for when the file is too small compared to + // the chunk factor doubled; we'll just read the whole file. + head_end = _file_size - 1; + skip_tail = true; + } else { + // here we compute the ending of the head piece and the beginning of + // the tail piece. each will be about checksum_edge in size. + head_end = minimum(_file_size / 2, double(checksum_edge)) - 1; + tail_start = _file_size - minimum(_file_size / 2, double(checksum_edge)); + } + + // read the head end of the file. + int size_read = 0; + outcome ret = to_read.read(chunk, int(head_end - head_start + 1), size_read); + if (ret != huge_file::OKAY) { +#ifdef DEBUG_FILE_INFO + LOG(astring("reading file failed: ") + full.raw()); +#endif + return false; // failed to read. + } + curr_sum = checksums::rolling_fletcher_checksum(curr_sum, chunk.observe(), + chunk.length()); + + // read the tail end of the file. + if (!skip_tail) { + to_read.seek(tail_start, byte_filer::FROM_START); + ret = to_read.read(chunk, int(tail_end - tail_start + 1), size_read); + if (ret != huge_file::OKAY) { +#ifdef DEBUG_FILE_INFO + LOG(astring("reading tail of file failed: ") + full.raw()); +#endif + return false; // failed to read. + } + curr_sum = checksums::rolling_fletcher_checksum(curr_sum, chunk.observe(), + chunk.length()); + } + + _checksum = curr_sum; + return true; +} + +int file_info::packed_size() const +{ + return filename::packed_size() + + structures::packed_size(_file_size) + + _time.packed_size() + + PACKED_SIZE_INT32 + + c_secondary.packed_size() + + structures::packed_size(c_attachment); +} + +void file_info::pack(byte_array &packed_form) const +{ + filename::pack(packed_form); + attach(packed_form, _file_size); + _time.pack(packed_form); + attach(packed_form, _checksum); + c_secondary.pack(packed_form); + attach(packed_form, c_attachment); +} + +bool file_info::unpack(byte_array &packed_form) +{ + if (!filename::unpack(packed_form)) + return false; + if (!detach(packed_form, _file_size)) + return false; + if (!_time.unpack(packed_form)) + return false; + if (!detach(packed_form, _checksum)) + return false; + if (!c_secondary.unpack(packed_form)) + return false; + if (!detach(packed_form, c_attachment)) + return false; + return true; +} + +file_info &file_info::operator = (const file_info &to_copy) +{ + if (this == &to_copy) + return *this; + (filename &)(*this) = (filename &)to_copy; + c_attachment = to_copy.c_attachment; + _time = to_copy._time; + _file_size = to_copy._file_size; + c_secondary = to_copy.c_secondary; + _checksum = to_copy._checksum; + return *this; +} + +} //namespace. + diff --git a/nucleus/library/filesystem/file_info.h b/nucleus/library/filesystem/file_info.h new file mode 100644 index 00000000..0e29be49 --- /dev/null +++ b/nucleus/library/filesystem/file_info.h @@ -0,0 +1,96 @@ +#ifndef FILE_INFO_CLASS +#define FILE_INFO_CLASS + +/*****************************************************************************\ +* * +* Name : file_info * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "filename.h" +#include "file_time.h" + +#include +#include + +namespace filesystem { + +//! Encapsulates some measures and calculations based on a file's contents. + +class file_info : public filename +{ +public: + //! this enum encapsulates how files may be compared. + enum file_similarity { + EQUAL_NAME = 0, // we assume name equality is pre-eminent and always required. + EQUAL_CHECKSUM = 0x1, // the files have the same checksum, however computed. + EQUAL_TIMESTAMP = 0x2, // the files have exactly equal timestamps. + EQUAL_FILESIZE = 0x4, // the files have the same sizes. + EQUAL_CHECKSUM_TIMESTAMP_FILESIZE = EQUAL_CHECKSUM & EQUAL_TIMESTAMP & EQUAL_FILESIZE + }; + + double _file_size; //!< the size of the file. + file_time _time; //!< the file's access time. + int _checksum; //!< the checksum for the file. + + file_info(); //!< blank constructor. + + file_info(const filename &to_copy, double file_size, + const file_time &time = file_time(), int checksum = 0); + //!< to get the real file size, timestamp and checksum, invoke the calculate method. + + file_info(const file_info &to_copy); + + virtual ~file_info(); + + DEFINE_CLASS_NAME("file_info"); + + file_info &operator = (const file_info &to_copy); + + basis::astring text_form() const; + + bool calculate(const basis::astring &prefix, bool just_size_n_time, + int checksum_edge = 1 * basis::KILOBYTE); + //!< fills in the correct file size and checksum information for this file. + /*!< note that the file must exist for this to work. if "just_size_n_time" + is true, then the checksum is not calculated. if the "prefix" is not + empty, then it is used as a directory path that needs to be added to + the filename to make it completely valid. this is common if the + filename is stored relative to a path. the "checksum_edge" is used for + checksum calculations; only 2 * checksum_edge bytes will be factored in, + each part from the head and tail ends of the file. */ + + const basis::astring &secondary() const; + //!< observes the alternate form of the name. + void secondary(const basis::astring &new_sec); + //!< accesses the alternate form of the name. + + const basis::byte_array &attachment() const; + //!< returns the chunk of data optionally attached to the file's info. + /*!< this supports extending the file's record with extra data that might + be needed during processing. */ + void attachment(const basis::byte_array &new_attachment); + //!< sets the optional chunk of data hooked up to the file's info. + + // standard streaming operations. + virtual int packed_size() const; + virtual void pack(basis::byte_array &packed_form) const; + virtual bool unpack(basis::byte_array &packed_form); + +private: + basis::astring c_secondary; //!< alternate filename for the main one. + basis::byte_array c_attachment; //!< extra information, if needed. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/file_time.cpp b/nucleus/library/filesystem/file_time.cpp new file mode 100644 index 00000000..5f05a4fb --- /dev/null +++ b/nucleus/library/filesystem/file_time.cpp @@ -0,0 +1,146 @@ +/*****************************************************************************\ +* * +* Name : file_time * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "file_time.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef __UNIX__ + #include +#endif +#ifdef __WIN32__ + #include +#endif + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +using namespace basis; + +namespace filesystem { + +file_time::file_time() : _when(0) { reset(""); } + +file_time::file_time(FILE *the_FILE) : _when(0) { reset(the_FILE); } + +file_time::file_time(const time_t &t) : _when(t) {} + +file_time::file_time(const astring &filename) +: _when(0) +{ + FILE *ptr = fopen(filename.s(), "r"); + if (ptr) { + reset(ptr); + fclose(ptr); + } +} + +file_time::~file_time() {} + +bool file_time::set_time(const basis::astring &filename) +{ + // prepare the access time and modified time to match our stamp. + utimbuf held_time; + held_time.actime = raw(); + held_time.modtime = raw(); + // stuff our timestamp on the file. + return !utime(filename.s(), &held_time); +} + +void file_time::reset(const time_t &t) { _when = t; } + +void file_time::reset(const astring &filename) +{ + FILE *ptr = fopen(filename.s(), "r"); + if (ptr) { + reset(ptr); + fclose(ptr); + } +} + +void file_time::reset(FILE *the_FILE_in) +{ + FUNCDEF("reset"); + _when = 0; + FILE *the_file = (FILE *)the_FILE_in; + if (!the_file) { + return; + } + struct stat stat_buffer; + if (fstat(fileno(the_file), &stat_buffer) < 0) { + LOG("stat failure on file"); + } + _when = stat_buffer.st_mtime; +} + +int file_time::compare(const file_time &b) const +{ + if (_when > b._when) return 1; + else if (_when == b._when) return 0; + else return -1; +} + +bool file_time::less_than(const orderable &ft2) const +{ + const file_time *cast = dynamic_cast(&ft2); + if (!cast) return false; + return bool(compare(*cast) < 0); +} + +bool file_time::equal_to(const equalizable &ft2) const +{ + const file_time *cast = dynamic_cast(&ft2); + if (!cast) return false; + return bool(compare(*cast) == 0); +} + +void file_time::text_form(basis::base_string &time_string) const +{ + time_string.assign(basis::astring(class_name()) + basis::a_sprintf("@%x", _when)); +} + +void file_time::readable_text_form(basis::base_string &time_string) const +{ + tm *now = localtime(&_when); // convert to time round hereparts. + time_string.assign(a_sprintf("%d.%02d.%02d %02d:%02d:%02d", + now->tm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec)); +} + +// magic value unfortunately, but this is the length of a packed string with 10 characters. +// we do this because the size of 2^32 in decimal requires that many characters, and we also +// want to just have a fixed length chunk for this. we are not worried about larger pieces +// than that because we cannot conveniently handle them anyhow. +int file_time::packed_size() const { return 11; } + +void file_time::pack(byte_array &packed_form) const +{ a_sprintf("%010d", _when).pack(packed_form); } + +bool file_time::unpack(byte_array &packed_form) +{ + astring tempo; + if (!tempo.unpack(packed_form)) return false; + _when = tempo.convert(0L); + return true; +} + +} //namespace + diff --git a/nucleus/library/filesystem/file_time.h b/nucleus/library/filesystem/file_time.h new file mode 100644 index 00000000..0e89d356 --- /dev/null +++ b/nucleus/library/filesystem/file_time.h @@ -0,0 +1,97 @@ +#ifndef FILE_TIME_CLASS +#define FILE_TIME_CLASS + +/*****************************************************************************\ +* * +* Name : file_time * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! A platform independent way to obtain the timestamp of a file. + +#include +#include +#include + +#include +#include + +namespace filesystem { + +class file_time +: public virtual basis::hoople_standard, + public virtual basis::orderable +{ +public: + file_time(); //!< sets up a bogus file_time object. + + file_time(FILE *the_FILE); + //!< sets up the file_time information given a the file stream of interest. + /*!< If the stream is NIL, then the file_time is set up with an invalid + time. */ + + file_time(const basis::astring &filename); + //!< this constructor operates on a file's name rather than a FILE stream. + + file_time(const time_t &init); + //!< starts the file_time with a particular "init" time. + +//hmmm: need a converter that sucks in an earth_time time_locus object. + + virtual ~file_time(); + + DEFINE_CLASS_NAME("file_time"); + + virtual void text_form(basis::base_string &time_string) const; + //!< returns a definitive but sorta ugly version of the file's time. + + virtual void readable_text_form(basis::base_string &time_string) const; + //!< sets "time_string" to a human readable form of the file's time. + + void reset(FILE *the_FILE); + //!< reacquires the time from a different FILE than constructed with. + /*!< this also can connect a FILE to the file_time object after using the + empty constructor. further, it can also be used to refresh a file's time + to account for changes in its timestamp. */ + + void reset(const basis::astring &filename); + //!< parallel version of reset() takes a file name instead of a stream. + + void reset(const time_t &init); + //!< parallel version of reset() takes a time_t instead of a stream. + + time_t raw() const { return _when; } + //!< provides the OS version of the file's timestamp. + + bool set_time(const basis::astring &filename); + //!< sets the time for the the "filename" to the currently held time. + + // Standard comparison operators between this file time and the file time + // "ft2". These are meaningless if either time is invalid. + virtual bool less_than(const basis::orderable &ft2) const; + virtual bool equal_to(const basis::equalizable &ft2) const; + + // supports streaming the time into and out of a byte array. + virtual int packed_size() const; + virtual void pack(basis::byte_array &packed_form) const; + virtual bool unpack(basis::byte_array &packed_form); + +private: + time_t _when; //!< our record of the file's timestamp. + + int compare(const file_time &ft2) const; + //!< root comparison function for all the operators. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/filename.cpp b/nucleus/library/filesystem/filename.cpp new file mode 100644 index 00000000..ef148ea8 --- /dev/null +++ b/nucleus/library/filesystem/filename.cpp @@ -0,0 +1,544 @@ +/*****************************************************************************\ +* * +* Name : filename * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// implementation note: the filename is kept canonicalized. any constructor +// or assignment operator should ensure this (except the blank constructor). + +#include "filename.h" + +#include +#include + +#include +#include +#include +#ifdef __UNIX__ + #include +#endif +#ifdef __WIN32__ + #include +#endif + +using namespace basis; +using namespace structures; + +class status_info : public stat +{ +}; + +namespace filesystem { + +#if defined(__WIN32__) || defined(__VMS__) + const char DEFAULT_SEPARATOR = '\\'; +#elif defined(__UNIX__) + const char DEFAULT_SEPARATOR = '/'; +#else + #error "We have no idea what the default path separator is." +#endif + +const char *NO_PARENT_DEFAULT = "."; + // used when no directory name can be popped off. + +filename::filename() +: astring(), + _had_directory(false) +{} + +filename::filename(const astring &name) +: astring(name), + _had_directory(true) +{ canonicalize(); } + +filename::filename(const astring &directory, const astring &name_of_file) +: astring(directory), + _had_directory(true) +{ + // if the directory is empty, use the current directory. + if (!directory) { + *this = astring(NO_PARENT_DEFAULT); + _had_directory = false; + } + // check for a slash on the end of the directory. add one if there is none + // currently. + bool add_slash = false; + if ( (directory[directory.end()] != '\\') + && (directory[directory.end()] != '/') ) add_slash = true; + if (add_slash) *this += DEFAULT_SEPARATOR; + *this += name_of_file; + canonicalize(); +} + +filename::filename(const filename &to_copy) +: astring(to_copy), + _had_directory(to_copy._had_directory) +{ canonicalize(); } + +filename::~filename() {} + +astring filename::default_separator() { return astring(DEFAULT_SEPARATOR, 1); } + +astring &filename::raw() { return *this; } + +const astring &filename::raw() const { return *this; } + +bool filename::good() const { return exists(); } + +bool filename::unlink() const { return ::unlink(observe()) == 0; } + +astring filename::null_device() +{ +#ifdef __WIN32__ + return "null:"; +#else + return "/dev/null"; +#endif +} + +bool filename::separator(char is_it) +{ return (is_it == pc_separator) || (is_it == unix_separator); } + +filename &filename::operator = (const filename &to_copy) +{ + if (this == &to_copy) return *this; + (astring &)(*this) = to_copy; + _had_directory = to_copy._had_directory; + return *this; +} + +filename &filename::operator = (const astring &to_copy) +{ + _had_directory = true; + if (this == &to_copy) return *this; + (astring &)(*this) = to_copy; + canonicalize(); + return *this; +} + +astring filename::pop() +{ + astring to_return = basename(); + filename parent_dir = parent(); + if (parent_dir.raw().equal_to(NO_PARENT_DEFAULT)) { + // we haven't gone anywhere. + return ""; // signal that nothing was removed. + } + *this = parent_dir; + return to_return; +} + +filename filename::parent() const { return dirname(); } + +void filename::push(const astring &to_push) +{ + *this = filename(*this, to_push); +} + +void filename::canonicalize() +{ + // turn all the non-default separators into the default. + bool found_sep = false; + for (int j = 0; j < length(); j++) { + if (separator(get(j))) { + found_sep = true; + put(j, DEFAULT_SEPARATOR); + } + } + + // if there wasn't a single directory separator, then they must not have + // specified any directory name for this filename (although it could itself + // be a directory). + if (!found_sep) _had_directory = false; + + // remove all occurrences of double separators except for the first + // double set, which could be a UNC filename. that's why the index below + // starts at one rather than zero. + bool saw_sep = false; + for (int i = 1; i < length(); i++) { + if (separator(get(i))) { + if (saw_sep) { + zap(i, i); + // two in a row is no good, except for the first two. + i--; // skip back one and try again. + continue; + } + saw_sep = true; + } else saw_sep = false; + } + + // we don't crop the last separator if the name's too small. for msdos + // names, that would be chopping a slash off the c:\ style name. + if (length() > 3) { + // zap any separators that are hiding on the end. + const int last = end(); + if (separator(get(last))) zap(last, last); + } else if ( (length() == 2) && (get(1) == ':') ) { + // special case for dos drive names. we turn it back into a valid + // directory rather than leaving it as just "X:". that form of the name + // means something else under dos/windows. + *this += astring(DEFAULT_SEPARATOR, 1); + } +} + +char filename::drive(bool interact_with_fs) const +{ + // first guess: if second letter's a colon, first letter's the drive. + if (length() < 2) + return '\0'; + if (get(1) == ':') + return get(0); + if (!interact_with_fs) + return '\0'; + + // otherwise, retrieve the file system's record for the file. + status_info fill; + if (!get_info(&fill)) + return '\0'; + return char('A' + fill.st_dev); +} + +astring filename::extension() const +{ + astring base(basename().raw()); + int posn = base.find('.', base.end(), true); + if (negative(posn)) + return ""; + return base.substring(posn + 1, base.length() - 1); +} + +astring filename::rootname() const +{ + astring base(basename().raw()); + int posn = base.find('.', base.end(), true); + if (negative(posn)) + return base; + return base.substring(0, posn - 1); +} + +bool filename::get_info(status_info *to_fill) const +{ + int ret = stat(observe(), to_fill); + if (ret) + return false; + return true; +} + +bool filename::is_directory() const +{ + status_info fill; + if (!get_info(&fill)) + return false; + return !!(fill.st_mode & S_IFDIR); +} + +bool filename::is_writable() const +{ + status_info fill; + if (!get_info(&fill)) + return false; + return !!(fill.st_mode & S_IWRITE); +} + +bool filename::is_readable() const +{ + status_info fill; + if (!get_info(&fill)) + return false; + return !!(fill.st_mode & S_IREAD); +} + +bool filename::is_executable() const +{ + status_info fill; + if (!get_info(&fill)) + return false; + return !!(fill.st_mode & S_IEXEC); +} + +int filename::find_last_separator(const astring &look_at) const +{ + int last_sep = -1; + int sep = 0; + while (sep >= 0) { + sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1); + if (sep >= 0) last_sep = sep; + } + return last_sep; +} + +filename filename::basename() const +{ + astring basename = *this; + int last_sep = find_last_separator(basename); + if (last_sep >= 0) basename.zap(0, last_sep); + return basename; +} + +filename filename::dirname() const +{ + astring dirname = *this; + int last_sep = find_last_separator(dirname); + // we don't accept ripping off the first slash. + if (last_sep >= 1) { + // we can rip the slash and suffix off to get the directory name. however, + // this might be in the form X: on windows. if they want the slash to + // remain, they can use the dirname that appends it. + dirname.zap(last_sep, dirname.end()); + } else { + if (get(0) == DEFAULT_SEPARATOR) { + // handle when we're up at the top of the filesystem. on unix, once + // you hit the root, you can keep going up but you still remain at + // the root. similarly on windoze, if there's no drive name in there. + dirname = astring(DEFAULT_SEPARATOR, 1); + } else { + // there's no slash at all in the filename any more. we assume that + // the directory is the current one, if no other information is + // available. this default is already used by some code. + dirname = NO_PARENT_DEFAULT; + } + } + return dirname; +} + +astring filename::dirname(bool add_slash) const +{ + astring tempname = dirname().raw(); + if (add_slash) tempname += DEFAULT_SEPARATOR; + return tempname; +} + +bool filename::exists() const +{ + if (is_directory()) + return true; + if (!length()) + return false; + return is_readable(); +/// byte_filer opened(observe(), "rb"); +/// return opened.good(); +} + +bool filename::legal_character(char to_check) +{ + switch (to_check) { + case ':': case ';': + case '\\': case '/': + case '*': case '?': case '$': case '&': case '|': + case '\'': case '"': case '`': + case '(': case ')': + case '[': case ']': + case '<': case '>': + case '{': case '}': + return false; + default: return true; + } +} + +void filename::detooth_filename(astring &to_clean, char replacement) +{ + for (int i = 0; i < to_clean.length(); i++) { + if (!legal_character(to_clean[i])) + to_clean[i] = replacement; + } +} + +int filename::packed_size() const +{ + return PACKED_SIZE_INT32 + astring::packed_size(); +} + +void filename::pack(byte_array &packed_form) const +{ + attach(packed_form, int(_had_directory)); + astring::pack(packed_form); +} + +bool filename::unpack(byte_array &packed_form) +{ + int temp; + if (!detach(packed_form, temp)) + return false; + _had_directory = temp; + if (!astring::unpack(packed_form)) + return false; + return true; +} + +void filename::separate(string_array &pieces) const +{ + pieces.reset(); + const astring &raw_form = raw(); + astring accumulator; // holds the names we find. + for (int i = 0; i < raw_form.length(); i++) { + if (separator(raw_form[i])) { + // this is a separator character, so eat it and add the accumulated + // string to the list. + if (!i || accumulator.length()) pieces += accumulator; + // now reset our accumulated text. + accumulator = astring::empty_string(); + } else { + // not a separator, so just accumulate it. + accumulator += raw_form[i]; + } + } + if (accumulator.length()) pieces += accumulator; +} + +void filename::join(const string_array &pieces) +{ + astring constructed_name; // we'll make a filename here. + for (int i = 0; i < pieces.length(); i++) { + constructed_name += pieces[i]; + if (!i || (i != pieces.length() - 1)) + constructed_name += DEFAULT_SEPARATOR; + } + *this = constructed_name; +} + +bool filename::base_compare_prefix(const filename &to_compare, + string_array &first, string_array &second) +{ + separate(first); + to_compare.separate(second); + // that case should never be allowed, since there are some bits missing + // in the name to be compared. + if (first.length() > second.length()) + return false; + + // compare each of the pieces. + for (int i = 0; i < first.length(); i++) { +#if defined(__WIN32__) || defined(__VMS__) + // case-insensitive compare. + if (!first[i].iequals(second[i])) + return false; +#else + // case-sensitive compare. + if (first[i] != second[i]) + return false; +#endif + } + return true; +} + +bool filename::compare_prefix(const filename &to_compare, astring &sequel) +{ + sequel = astring::empty_string(); // clean our output parameter. + string_array first; + string_array second; + if (!base_compare_prefix(to_compare, first, second)) + return false; + + // create the sequel string. + int extra_strings = second.length() - first.length(); + for (int i = second.length() - extra_strings; i < second.length(); i++) { + sequel += second[i]; + if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR; + } + + return true; +} + +bool filename::compare_prefix(const filename &to_compare) +{ + string_array first; + string_array second; + return base_compare_prefix(to_compare, first, second); +} + +bool filename::base_compare_suffix(const filename &to_compare, + string_array &first, string_array &second) +{ + separate(first); + to_compare.separate(second); + // that case should never be allowed, since there are some bits missing + // in the name to be compared. + if (first.length() > second.length()) + return false; + + // compare each of the pieces. + for (int i = first.length() - 1; i >= 0; i--) { +//clean up this computation; the difference in lengths is constant--use that. + int distance_from_end = first.length() - 1 - i; + int j = second.length() - 1 - distance_from_end; +#if defined(__WIN32__) || defined(__VMS__) + // case-insensitive compare. + if (!first[i].iequals(second[j])) + return false; +#else + // case-sensitive compare. + if (first[i] != second[j]) + return false; +#endif + } + return true; +} + +bool filename::compare_suffix(const filename &to_compare, astring &prequel) +{ + prequel = astring::empty_string(); // clean our output parameter. + string_array first; + string_array second; + if (!base_compare_suffix(to_compare, first, second)) + return false; + + // create the prequel string. + int extra_strings = second.length() - first.length(); + for (int i = 0; i < extra_strings; i++) { + prequel += second[i]; + if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR; + } + return true; +} + +bool filename::compare_suffix(const filename &to_compare) +{ + string_array first; + string_array second; + return base_compare_suffix(to_compare, first, second); +} + +bool filename::chmod(int write_mode, int owner_mode) const +{ + int chmod_value = 0; +#ifdef __UNIX__ + if (write_mode & ALLOW_READ) { + if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR; + if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP; + if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH; + } + if (write_mode & ALLOW_WRITE) { + if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR; + if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP; + if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH; + } +//// chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; +#elif defined(__WIN32__) + if (write_mode & ALLOW_READ) { + chmod_value |= _S_IREAD; + } + if (write_mode & ALLOW_WRITE) { + chmod_value |= _S_IWRITE; + } +#else + #error unsupported OS type currently. +#endif + int chmod_result = ::chmod(raw().s(), chmod_value); + if (chmod_result) { +// LOG(astring("there was a problem changing permissions on ") + raw()); + return false; + } + return true; +} + +} //namespace. + diff --git a/nucleus/library/filesystem/filename.h b/nucleus/library/filesystem/filename.h new file mode 100644 index 00000000..a6018ef5 --- /dev/null +++ b/nucleus/library/filesystem/filename.h @@ -0,0 +1,247 @@ +#ifndef FILENAME_CLASS +#define FILENAME_CLASS + +/*****************************************************************************\ +* * +* Name : filename * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +// forward declarations. +class status_info; + +//hmmm: this doesn't really belong here, does it. + +// define useful constant for filesystem path length. +#ifndef MAX_ABS_PATH + #ifdef __WIN32__ + #include + #define MAX_ABS_PATH MAX_PATH + #else + #ifdef __APPLE__ + #include + #else + #include + #endif + #define MAX_ABS_PATH PATH_MAX + #endif +#endif + + +namespace filesystem { + +//! Provides operations commonly needed on file names. + +class filename : public basis::astring, public virtual basis::packable +{ +public: + filename(); //!< blank constructor. + filename(const basis::astring &name); + //!< creates a filename from any part of a full pathname, if possible. + /*!< if the name contains quotes, they are stripped out. */ + filename(const basis::astring &directory, const basis::astring &name_of_file); + //!< constructs a filename from a "directory" and the "name_of_file". + /*!< the "name_of_file" can itself be a directory. */ + filename(const filename &to_copy); //!< copy constructor. + + virtual ~filename(); + + bool good() const; + //!< returns true if the filename seems to be valid. + /*!< this means that not only was the pathname parsed and found valid, + but the file actually exists. */ + + const basis::astring &raw() const; + //!< returns the astring that we're holding onto for the path. + basis::astring &raw(); + //!< accesses the astring that we're holding onto for the path. + /*!< important note: if you change the string with this non-const raw() + method, you MUST call canonicalize() on it again afterwards. */ + + filename &operator = (const filename &to_copy); + //!< provides assignment for this object, plus a simple string. + filename &operator = (const basis::astring &to_copy); + //!< provides assignment for this object, plus a simple string. + /*!< the latter version invokes canonicalize to clean the string up. */ + + void canonicalize(); + //!< cleans up the filename as needed for the current operating system. + /*!< reforms the name by replacing any alternate directory separators with + the operating system's preferred character. */ + + bool exists() const; + //!< returns true if the file exists. + + bool unlink() const; + //!< actually removes the file, if possible. + /*!< if the file was successfully deleted, then true is returned. */ + + filename parent() const; + //!< returns the parent filename for this one. + + basis::astring pop(); + //!< removes the deepest component of the pathname. + /*!< the component might be a file or directory name, but popping beyond + the top-level directory will not succeed. the returned string contains + the component that was removed. it will be a blank string if nothing + could be popped. */ + + void push(const basis::astring &to_push); + //!< pushes a new filename onto the current pathname. + /*!< this only makes sense as a real pathname if this is currently a + directory name and the component "to_push" is a child of that directory + (or one intends to create that component as a child). this is the + opposite of pop. */ + + filename basename() const; + //!< returns the base of the filename; no directory. + filename dirname() const; + //!< returns the directory for the filename. + /*!< if no directory name can be found in the filename, then "." is + returned. */ + basis::astring dirname(bool add_slash) const; + //!< returns the directory for the filename and optionally adds a slash. + /*!< if "add_slash" is true, then the default directory separator will be + present on the end of the string. */ + bool had_directory() const { return _had_directory; } + //!< returns true if the name that we were given had a non-empty directory. + /*!< this allows one to distinguish between a file with the current + directory (.) attached and a file with no directory specified. */ + + char drive(bool interact_with_fs = false) const; + //!< returns the drive letter for the file, without the colon. + /*!< this only makes sense for a fully qualified MS-DOS style name. if no + drive letter is found, then '\0' is returned. if "interact_with_fs" is + true, then the file system will be checked for the actual drive if no + drive letter was found in the contents. */ + + basis::astring extension() const; + //!< returns the extension for the file, if one is present. + + basis::astring rootname() const; + //!< returns the root part of the basename without an extension. + + // status functions return true if the characteristic embodied in + // the name is also true. + + bool is_directory() const; + bool is_writable() const; + bool is_readable() const; + bool is_executable() const; + + enum write_modes { + ALLOW_NEITHER = 0x0, + ALLOW_READ = 0x1, ALLOW_WRITE = 0x2, + ALLOW_BOTH = ALLOW_READ | ALLOW_WRITE + }; + + enum ownership_modes { + NO_RIGHTS = 0x0, + USER_RIGHTS = 0x1, GROUP_RIGHTS = 0x2, OTHER_RIGHTS = 0x4, + ALL_RIGHTS = USER_RIGHTS | GROUP_RIGHTS | OTHER_RIGHTS + }; + + bool chmod(int write_mode, int owner_mode) const; + //!< changes the access rights on the file. + + //! the default separator for directories per operating system. + /*! the PC uses the backward slash to separate file and directory names from + each other, while Unix uses the forward slash. */ + enum directory_separator { pc_separator = '\\', unix_separator = '/' }; + + static bool separator(char is_it); + //!< returns true if the character "is_it" in question is a separator. + + static basis::astring default_separator(); + //!< returns the default separator character for this OS. + + static bool legal_character(char to_check); + //!< returns true if "to_check" is a valid character in a filename. + /*!< this does not consider separator characters; it only looks at the + the name components. also, it is appropriate for the union of the + operating systems we support. */ + + static void detooth_filename(basis::astring &to_clean, char replacement = '_'); + //!< takes any known illegal file system characters out of "to_clean". + /*!< this prepares "to_clean" for use as a component in a larger filename + by ensuring that the file system will not reject the name (as long as a + suitable directory path is prepended to the name and permissions allow + the file to be created or accessed). the "replacement" is used as the + character that is substituted instead of illegal characters. */ + + void separate(structures::string_array &pieces) const; + //!< breaks the filename into its component directories. + /*!< this returns an array containing the component names. the last + component, unless the filename held is actually a directory, should be the + name of the file. if the first character is a directory, then the first + component will be empty. */ + + void join(const structures::string_array &pieces); + //!< undoes a separate() operation to get the filename back. + /*!< "this" is set to a filename made from each of the "pieces". if there + are any directory separators inside the pieces, then they will be removed + by canonicalize(). */ + + // these implement the packing functionality. + virtual void pack(basis::byte_array &packed_form) const; + virtual bool unpack(basis::byte_array &packed_form); + virtual int packed_size() const; + + bool compare_prefix(const filename &to_compare, basis::astring &sequel); + //!< examines "this" filename to see if it's a prefix of "to_compare". + /*!< this returns true if all of "this" is the same as the first portion + of "to_compare". that is, if "this" is a prefix of "to_compare", then + true is returned. this will always fail if there are fewer components in + "to_compare". it will always succeed if the two filenames are identical. + on success, the "sequel" is set to the portion of "to_compare" that's + not included in this filename. */ + + bool compare_prefix(const filename &to_compare); + //!< this simpler form doesn't bother with computing the sequel. + + bool compare_suffix(const filename &to_compare, basis::astring &prequel); + //!< compares the back end of a filename to this. + /*!< this is similar to compare_prefix() but it checks to see if the + back end of "this" filename is the same as "to_compare". if "this" is + longer than "to_compare", then failure occurs. only if all of the bits + in "this" are seen in the back of "to_compare" is true returned. */ + + bool compare_suffix(const filename &to_compare); + + static basis::astring null_device(); + //!< returns the name for the black hole device that consumes all input, i.e. /dev/null. + +private: + bool _had_directory; //!< true if _some_ directory was specified on init. +/// basis::astring *_contents; //!< the full path is held here. + + int find_last_separator(const basis::astring &look_at) const; + //!< locates the last separator character in the filename. + + bool get_info(status_info *to_fill) const; + //!< returns information for the filename. + + // helper functions do the real work for comparing. + bool base_compare_prefix(const filename &to_compare, structures::string_array &first, + structures::string_array &second); + bool base_compare_suffix(const filename &to_compare, structures::string_array &first, + structures::string_array &second); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/filename_list.cpp b/nucleus/library/filesystem/filename_list.cpp new file mode 100644 index 00000000..24d1522e --- /dev/null +++ b/nucleus/library/filesystem/filename_list.cpp @@ -0,0 +1,170 @@ +/*****************************************************************************\ +* * +* Name : filename_list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "filename_list.h" + +#include +#include + +using namespace basis; +using namespace structures; +using namespace textual; + +namespace filesystem { + +filename_list::filename_list() : amorph() {} + +filename_list &filename_list::operator =(const filename_list &to_copy) +{ + if (this == &to_copy) return *this; + reset(); + for (int i = 0; i < to_copy.elements(); i++) { + append(new file_info(*to_copy.get(i))); + } + return *this; +} + +int filename_list::total_files() const { return elements(); } + +int filename_list::packed_size() const +{ return amorph_packed_size(*this); } + +void filename_list::pack(byte_array &packed_form) const +{ amorph_pack(packed_form, *this); } + +bool filename_list::unpack(byte_array &packed_form) +{ return amorph_unpack(packed_form, *this); } + +double filename_list::total_size() const +{ + double to_return = 0; + for (int i = 0; i < elements(); i++) + to_return += get(i)->_file_size; + return to_return; +} + +bool filename_list::calculate_progress(const filename &file, + double current_offset, int ¤t_file, double ¤t_size) +{ + current_file = 0; + current_size = 0; + int posn = locate(file); + if (negative(posn)) { + if (file.raw().t()) return false; // not a member. + // they're at the start of the run. + current_file = 1; + return true; + } + current_file = posn + 1; // position zero in array means file number 1. + double size_finished = 0; + // iterate on all files before the current one. + for (int i = 0; i < posn; i++) { + size_finished += get(i)->_file_size; + } + current_size = size_finished + current_offset; + return true; +} + +filename_list &filename_list::operator = (const string_array &to_copy) +{ + reset(); + for (int i = 0; i < to_copy.length(); i++) { + append(new file_info(to_copy[i], 0)); + } + return *this; +} + +void filename_list::fill(string_array &to_fill) +{ + to_fill.reset(); + for (int i = 0; i < elements(); i++) { + to_fill += get(i)->raw(); + } +} + +const file_info *filename_list::find(const filename &to_check) const +{ + for (int i = 0; i < elements(); i++) { +#if defined(__WIN32__) || defined(__VMS__) + if (to_check.raw().iequals(get(i)->raw())) return get(i); +#else + if (to_check.raw() == get(i)->raw()) return get(i); +#endif + } + return NIL; +} + +int filename_list::locate(const filename &to_find) const +{ + for (int i = 0; i < elements(); i++) { +#if defined(__WIN32__) || defined(__VMS__) + if (to_find.raw().iequals(get(i)->raw())) return i; +#else + if (to_find.raw() == get(i)->raw()) return i; +#endif + } + return common::NOT_FOUND; +} + +bool filename_list::member(const filename &to_check) const +{ + for (int i = 0; i < elements(); i++) { +#if defined(__WIN32__) || defined(__VMS__) + if (to_check.raw().iequals(get(i)->raw())) return true; +#else + if (to_check.raw() == get(i)->raw()) return true; +#endif + } + return false; +} + +bool filename_list::member_with_state(const file_info &to_check, file_info::file_similarity how) +{ + for (int i = 0; i < elements(); i++) { +#if defined(__WIN32__) || defined(__VMS__) + if (to_check.raw().iequals(get(i)->raw())) { +#else + if (to_check.raw() == get(i)->raw()) { +#endif + // once we have matched a name, the other checks will cause us to + // reject any other potential matches after this one if the requested + // check fails. + if ((how & file_info::EQUAL_FILESIZE) && (to_check._file_size != get(i)->_file_size) ) + return false; + if ((how & file_info::EQUAL_TIMESTAMP) && (to_check._time != get(i)->_time) ) + return false; + if ((how & file_info::EQUAL_CHECKSUM) && (to_check._checksum != get(i)->_checksum) ) + return false; + return true; + } + } + return false; +} + +astring filename_list::text_form() const +{ + astring to_return; +//hmmm: a length limit might be nice? + for (int i = 0; i < elements(); i++) { + to_return += a_sprintf("%d. ", i + 1); + if (get(i)) + to_return += get(i)->text_form(); + if (i != elements() - 1) + to_return += parser_bits::platform_eol_to_chars(); + } + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/filesystem/filename_list.h b/nucleus/library/filesystem/filename_list.h new file mode 100644 index 00000000..f5834d79 --- /dev/null +++ b/nucleus/library/filesystem/filename_list.h @@ -0,0 +1,90 @@ +#ifndef FILENAME_LIST_CLASS +#define FILENAME_LIST_CLASS + +/*****************************************************************************\ +* * +* Name : filename_list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! Implements a list of filenames. +/*! + This is based on an amorph so that adding to the list is efficient. + The underlying type held is actually a file_info rather than a filename. + This should not impose much extra overhead. + + Note: this is a heavyweight header; it shouldn't be used in other headers. +*/ + +#include "file_info.h" + +#include +#include + +namespace filesystem { + +class filename_list +: public structures::amorph, public virtual basis::packable +{ +public: + filename_list(); + + filename_list &operator =(const filename_list &to_copy); + + int total_files() const; + //!< returns the number of files currently held in the list. + + double total_size() const; + //!< returns the full size of all files listed. + + bool calculate_progress(const filename &file, double current_offset, + int ¤t_file, double ¤t_size); + //!< given the last "file" and position, this returns current positioning. + /*!< the "current_file" is set to the index of the "file" in the list + (using 1-based numbering for a 1,2,3,... series), and the "current_size" + is set to the total amount of bytes processed so far. */ + + filename_list &operator = (const structures::string_array &to_copy); + + void fill(structures::string_array &to_fill); + //!< stuffs the array "to_fill" with the filesnames from our list. + + const file_info *find(const filename &to_check) const; + //!< locates the record of information for the filename "to_check". + /*!< do not modify the returned object. it contains the current state + of the file in question. if the file wasn't in the list, then NIL is + returned. */ + + int locate(const filename &to_find) const; + //! finds the index for "to_find" or returns a negative number. + + bool member(const filename &to_check) const; + //!< returns true if "to_check" is listed here. + + bool member_with_state(const file_info &to_check, file_info::file_similarity comparison_method); + //!< returns true if the file "to_check" exists in the list with appropriate equivalence. + /*!< this will fail if the file name isn't present at all. and then it will also not return + true if the size is different (when EQUAL_FILESIZE is true), when the timestamp is different + (when EQUAL_TIMESTAMP is true), and when the checksum is different (you get the idea). */ + + basis::astring text_form() const; + + virtual int packed_size() const; + + virtual void pack(basis::byte_array &packed_form) const; + + virtual bool unpack(basis::byte_array &packed_form); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/filename_tree.cpp b/nucleus/library/filesystem/filename_tree.cpp new file mode 100644 index 00000000..04a42030 --- /dev/null +++ b/nucleus/library/filesystem/filename_tree.cpp @@ -0,0 +1,52 @@ +/*****************************************************************************\ +* * +* Name : filename_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "filename_tree.h" + +#include + +using namespace basis; +using namespace nodes; +using namespace structures; + +namespace filesystem { + +nodes::packable_tree *fname_tree_creator::create() { return new filename_tree; } + +////////////// + +filename_tree::filename_tree() : _depth(0) {} + +filename_tree::~filename_tree() { _dirname = ""; _files.reset(); } + +int filename_tree::packed_size() const { + return PACKED_SIZE_INT32 + _dirname.packed_size() + _files.packed_size(); +} + +void filename_tree::pack(byte_array &packed_form) const { + structures::attach(packed_form, _depth); + _dirname.pack(packed_form); + _files.pack(packed_form); +} + +bool filename_tree::unpack(byte_array &packed_form) { + if (!structures::detach(packed_form, _depth)) return false; + if (!_dirname.unpack(packed_form)) return false; + if (!_files.unpack(packed_form)) return false; + return true; +} + +} //namespace. + + diff --git a/nucleus/library/filesystem/filename_tree.h b/nucleus/library/filesystem/filename_tree.h new file mode 100644 index 00000000..7d857d06 --- /dev/null +++ b/nucleus/library/filesystem/filename_tree.h @@ -0,0 +1,63 @@ +#ifndef FILENAME_TREE_CLASS +#define FILENAME_TREE_CLASS + +/*****************************************************************************\ +* * +* Name : filename_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! This is a support class for the directory_tree. +/*! + This class has been exported from directory_tree's implementation to + avoid redundant code for iteration and such. + Note: this is a heavy-weight header that should not be included in other + headers. +*/ + +#include "filename_list.h" + +#include + +namespace filesystem { + +class filename_tree : public nodes::packable_tree +{ +public: + filename _dirname; //!< the full directory name at this position. + filename_list _files; //!< the filenames that are at this node in the tree. + int _depth; //!< how far below root node are we. + + filename_tree(); + + virtual ~filename_tree(); + + virtual int packed_size() const; + + virtual void pack(basis::byte_array &packed_form) const; + + virtual bool unpack(basis::byte_array &packed_form); +}; + +//! this is the tree factory used in the recursive_unpack. +/*! it meets our needs for regenerating these objects from a streamed form. */ +class fname_tree_creator : public nodes::packable_tree_factory +{ +public: +//// virtual ~fname_tree_creator() {} + virtual nodes::packable_tree *create(); + //!< implements the create() method for filename_trees to support packing. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/heavy_file_ops.cpp b/nucleus/library/filesystem/heavy_file_ops.cpp new file mode 100644 index 00000000..1e63ba31 --- /dev/null +++ b/nucleus/library/filesystem/heavy_file_ops.cpp @@ -0,0 +1,334 @@ +/*****************************************************************************\ +* * +* Name : heavy file operations * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "directory.h" +#include "filename.h" +#include "filename_list.h" +#include "heavy_file_ops.h" +#include "huge_file.h" + +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace filesystem { + +//#define DEBUG_HEAVY_FILE_OPS + // uncomment for noisier debugging. + +#undef LOG +#ifdef DEBUG_HEAVY_FILE_OPS + #include + #define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) +#else + #define LOG(s) {if(!!s){}} +#endif + +////////////// + +file_transfer_header::file_transfer_header(const file_time &time_stamp) +: _filename(), + _byte_start(0), + _length(0), + _time(time_stamp) +{ +} + +astring file_transfer_header::text_form() const +{ + astring time_text; + _time.text_form(time_text); + return astring("file=") + _filename + + a_sprintf(" start=%d len=%d stamp=", _byte_start, _length) + + time_text; +} + +astring file_transfer_header::readable_text_form() const +{ + astring time_text; + _time.readable_text_form(time_text); + return _filename + + a_sprintf(" [%d bytes, mod ", _length) + + time_text + "]"; +} + +void file_transfer_header::pack(byte_array &packed_form) const +{ + _filename.pack(packed_form); + attach(packed_form, _byte_start); + attach(packed_form, _length); + _time.pack(packed_form); +} + +bool file_transfer_header::unpack(byte_array &packed_form) +{ + if (!_filename.unpack(packed_form)) return false; + if (!detach(packed_form, _byte_start)) return false; + if (!detach(packed_form, _length)) return false; + if (!_time.unpack(packed_form)) return false; + return true; +} + +int file_transfer_header::packed_size() const +{ +byte_array temp; +attach(temp, _byte_start); +//hmmm: really ugly above; we should get a more exact way to know the size of +// packed doubles. + return _filename.length() + 1 + + temp.length() + + sizeof(int) + + _time.packed_size(); +} + +////////////// + +const size_t heavy_file_operations::COPY_CHUNK_FACTOR = 1 * MEGABYTE; + +size_t heavy_file_operations::copy_chunk_factor() +{ return COPY_CHUNK_FACTOR; } + +heavy_file_operations::~heavy_file_operations() {} + // we only need this due to our use of the root_object class_name support. + +const char *heavy_file_operations::outcome_name(const outcome &to_name) +{ + switch (to_name.value()) { + case SOURCE_MISSING: return "SOURCE_MISSING"; + case TARGET_ACCESS_ERROR: return "TARGET_ACCESS_ERROR"; + case TARGET_DIR_ERROR: return "TARGET_DIR_ERROR"; + default: return common::outcome_name(to_name); + } +} + +outcome heavy_file_operations::copy_file(const astring &source, + const astring &destination, int copy_chunk_factor) +{ +#ifdef DEBUG_HEAVY_FILE_OPS + FUNCDEF("copy_file"); +#endif + // check that the source exists... + filename source_path(source); + if (!source_path.exists()) return SOURCE_MISSING; + file_time source_time(source_path); // get the time on the source. + + // make sure the target directory exists... + filename target_path(destination); + filename targ_dir = target_path.dirname(); + if (!directory::recursive_create(targ_dir.raw())) return TARGET_DIR_ERROR; + + // open the source for reading. + huge_file source_file(source, "rb"); + if (!source_file.good()) return SOURCE_MISSING; +//hmmm: could be source is not accessible instead. + + // open target file for writing. + huge_file target_file(destination, "wb"); + if (!target_file.good()) return TARGET_ACCESS_ERROR; + + byte_array chunk; + int bytes_read = 0; + outcome ret; + while ( (ret = source_file.read(chunk, copy_chunk_factor, bytes_read)) + == huge_file::OKAY) { + int bytes_stored; + ret = target_file.write(chunk, bytes_stored); + if (bytes_stored != bytes_read) return TARGET_ACCESS_ERROR; + if (source_file.eof()) break; // time to escape. + } + + // set the time on the target file from the source's time. + source_time.set_time(target_path); + +#ifdef DEBUG_HEAVY_FILE_OPS + astring time; + source_time.text_form(time); + LOG(astring("setting file time for ") + source + " to " + time); +#endif + + return OKAY; +} + +outcome heavy_file_operations::write_file_chunk(const astring &target, + double byte_start, const byte_array &chunk, bool truncate, + int copy_chunk_factor) +{ +#ifdef DEBUG_HEAVY_FILE_OPS +// FUNCDEF("write_file_chunk"); +#endif + if (byte_start < 0) return BAD_INPUT; + + filename targ_name(target); + if (!directory::recursive_create(targ_name.dirname().raw())) + return TARGET_DIR_ERROR; + + if (!targ_name.exists()) { + huge_file target_file(target, "w"); + } + + huge_file target_file(target, "r+b"); + // open the file for updating (either read or write). + if (!target_file.good()) return TARGET_ACCESS_ERROR; + double curr_len = target_file.length(); + + if (curr_len < byte_start) { + byte_array new_chunk; + while (curr_len < byte_start) { + target_file.seek(0, byte_filer::FROM_END); // go to the end of the file. + new_chunk.reset(minimum(copy_chunk_factor, + int(curr_len - byte_start + 1))); + int written; + outcome ret = target_file.write(new_chunk, written); + if (written < new_chunk.length()) return TARGET_ACCESS_ERROR; + curr_len = target_file.length(); + } + } + target_file.seek(byte_start, byte_filer::FROM_START); + // jump to the proper location in the file. + int wrote; + outcome ret = target_file.write(chunk, wrote); + if (wrote != chunk.length()) return TARGET_ACCESS_ERROR; + if (truncate) { + target_file.truncate(); + } + return OKAY; +} + +bool heavy_file_operations::advance(const filename_list &to_transfer, + file_transfer_header &last_action) +{ +#ifdef DEBUG_HEAVY_FILE_OPS + FUNCDEF("advance"); +#endif + int indy = to_transfer.locate(last_action._filename); + if (negative(indy)) return false; // error. + if (indy == to_transfer.elements() - 1) return false; // done. + const file_info *currfile = to_transfer.get(indy + 1); + last_action._filename = currfile->raw(); + last_action._time = currfile->_time; + +#ifdef DEBUG_HEAVY_FILE_OPS + if (currfile->_time == file_time(time_t(0))) + LOG(astring("failed for ") + currfile->raw() + " -- has zero file time"); +#endif + + last_action._byte_start = 0; + last_action._length = 0; + return true; +} + +outcome heavy_file_operations::buffer_files(const astring &source_root, + const filename_list &to_transfer, file_transfer_header &last_action, + byte_array &storage, int maximum_bytes) +{ +#ifdef DEBUG_HEAVY_FILE_OPS +// FUNCDEF("buffer_files"); +#endif + storage.reset(); // clear out the current contents. + + if (!to_transfer.elements()) { + // we seem to be done. + return OKAY; + } + + outcome to_return = OKAY; + + // start filling the array with bytes from the files. + while (storage.length() < maximum_bytes) { + double remaining_in_array = maximum_bytes - storage.length() + - last_action.packed_size(); + if (remaining_in_array < 128) { + // ensure that we at least have a reasonable amount of space left + // for storing into the array. + break; + } + + // find the current file we're at, as provided in record. + if (!last_action._filename) { + // no filename yet. assume this is the first thing we've done. + const file_info *currfile = to_transfer.get(0); + last_action._filename = currfile->raw(); + last_action._time = currfile->_time; + last_action._byte_start = 0; + last_action._length = 0; + } + + const file_info *found = to_transfer.find(last_action._filename); + if (!found) { + // they have referenced a file that we don't have. that's bad news. + return BAD_INPUT; + } + + astring full_file = source_root + "/" + last_action._filename; + huge_file current(full_file, "rb"); + if (!current.good()) { + // we need to skip this file. + if (!advance(to_transfer, last_action)) break; + continue; + } + + if ((last_action._byte_start + last_action._length >= current.length()) + && current.length()) { + // this file is done now. go to the next one. + if (!advance(to_transfer, last_action)) break; + continue; + } + + // calculate the largest piece remaining of that file that will fit in the + // allotted space. + double new_start = last_action._byte_start + last_action._length; + double remaining_in_file = current.length() - new_start; + if (remaining_in_file < 0) remaining_in_file = 0; + double new_len = minimum(remaining_in_file, remaining_in_array); + + // pack this new piece of the file. + current.seek(new_start, byte_filer::FROM_START); + byte_array new_chunk; + int bytes_read = 0; + outcome ret = current.read(new_chunk, int(new_len), bytes_read); + if (bytes_read != new_len) { + if (!bytes_read) { + // some kind of problem reading the file. + if (!advance(to_transfer, last_action)) break; + continue; + } +//why would this happen? just complain, i guess. + } + + // update the record since it seems we're successful here. + last_action._byte_start = new_start; + last_action._length = int(new_len); + + // add in this next new chunk of file. + last_action.pack(storage); // add the header. + storage += new_chunk; // add the new stuff. + + if (!current.length()) { + // ensure we don't get stuck redoing zero length files, which we allowed + // to go past their end above (since otherwise we'd never see them). + if (!advance(to_transfer, last_action)) break; + continue; + } + + // just keep going, if there's space... + } + + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/filesystem/heavy_file_ops.h b/nucleus/library/filesystem/heavy_file_ops.h new file mode 100644 index 00000000..add49683 --- /dev/null +++ b/nucleus/library/filesystem/heavy_file_ops.h @@ -0,0 +1,122 @@ +#ifndef HEAVY_FILE_OPERATIONS_CLASS +#define HEAVY_FILE_OPERATIONS_CLASS + +/*****************************************************************************\ +* * +* Name : heavy file operations * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "filename_list.h" + +#include +#include + +namespace filesystem { + +//! describes one portion of an ongoing file transfer. +/*! this is just a header describing an attached byte package. it is expected +that the bytes follow this in the communication stream. */ + +class file_transfer_header : public basis::packable +{ +public: + basis::astring _filename; //!< the name of the file being transferred. + double _byte_start; //!< the starting location in the file being sent. + int _length; //!< the length of the transferred piece. + file_time _time; //!< the timestamp on the file. + + DEFINE_CLASS_NAME("file_transfer_header"); + + file_transfer_header(const file_time &time_stamp); + //!< refactored to force addition of the time_stamp. + + virtual void pack(basis::byte_array &packed_form) const; + virtual bool unpack(basis::byte_array &packed_form); + + virtual int packed_size() const; + + basis::astring text_form() const; + +//hmmm: this could live in lots of other places. file_info for one. + basis::astring readable_text_form() const; + //!< a nicer formatting of the information. +}; + +////////////// + +//! Provides serious file operations, such as copy and partial writing. + +class heavy_file_operations : public virtual basis::root_object +{ +public: + virtual ~heavy_file_operations(); + + enum outcomes { + OKAY = basis::common::OKAY, + BAD_INPUT = basis::common::BAD_INPUT, +// GARBAGE = basis::common::GARBAGE, +// NOT_FOUND = basis::common::NOT_FOUND, +// NONE_READY = basis::common::NONE_READY, +// FAILURE = basis::common::FAILURE, + DEFINE_OUTCOME(SOURCE_MISSING, -43, "The source file is not accessible"), + DEFINE_OUTCOME(TARGET_DIR_ERROR, -44, "The target's directory could not " + "be created"), + DEFINE_OUTCOME(TARGET_ACCESS_ERROR, -45, "The target file could not be " + "created") + }; + static const char *outcome_name(const basis::outcome &to_name); + + DEFINE_CLASS_NAME("heavy_file_operations"); + + static const size_t COPY_CHUNK_FACTOR; + //!< the default copy chunk size for the file copy method. + static size_t copy_chunk_factor(); + //!< method can be exported for use by shared libs. + + static basis::outcome copy_file(const basis::astring &source, const basis::astring &destination, + int copy_chunk_factor = copy_chunk_factor()); + //!< copies a file from the "source" location to the "destination". + /*!< the outcomes could be from this class or from common::outcomes. + the "copy_chunk_factor" is the read buffer size to use while copying. */ + + static basis::outcome write_file_chunk(const basis::astring &target, double byte_start, + const basis::byte_array &chunk, bool truncate = true, + int copy_chunk_factor = copy_chunk_factor()); + //!< stores a chunk of bytes into the "target" file. + /*!< writes the content stored in "chunk" into the file "target" at the + position "byte_start". the entire "chunk" will be used, which means the + file will either be that much larger or have the space between byte_start + and (byte_start + chunk.length() - 1) replaced. if the file is not yet as + large as "byte_start", then it will be expanded appropriately. if + "truncate" is true, then any contents past the new chunk are dropped from + the file. */ + + static basis::outcome buffer_files(const basis::astring &source_root, + const filename_list &to_transfer, file_transfer_header &last_action, + basis::byte_array &storage, int maximum_bytes); + //!< reads files in "to_transfer" and packs them into a "storage" buffer. + /*!< the maximum size allowed in storage is "maximum_bytes". the record + of the last file piece stored in "last_action" allows the next chunk + to be sent in subsequent calls. note that the buffer "storage" is cleared + out before bytes are stored into it; this is not an additive operation. */ + +private: + static bool advance(const filename_list &to_transfer, file_transfer_header &last_action); + //!< advances to the next file in the transfer list "to_transfer". +}; + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/huge_file.cpp b/nucleus/library/filesystem/huge_file.cpp new file mode 100644 index 00000000..4c218c56 --- /dev/null +++ b/nucleus/library/filesystem/huge_file.cpp @@ -0,0 +1,280 @@ +/*****************************************************************************\ +* * +* Name : huge_file * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_filer.h" +#include "huge_file.h" + +#include +#include +#include + +#include + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +//#define DEBUG_HUGE_FILE + // uncomment for noisy version. + +using namespace basis; + +namespace filesystem { + +huge_file::huge_file(const astring &filename, const astring &permissions) +: _real_file(new byte_filer(filename, permissions)), + _file_pointer(0) +{ +} + +huge_file::~huge_file() +{ + WHACK(_real_file); +} + +void huge_file::flush() { _real_file->flush(); } + +bool huge_file::truncate() { return _real_file->truncate(); } + +double huge_file::length() +{ + FUNCDEF("length"); + +//trying to read to see if we're past endpoint. +// if this approach works, length may want to close and reopen file for +// reading, since we can't add any bytes to it for writing just to find +// the length out. + + + double save_posn = _file_pointer; + // skip to the beginning of the file so we can try to find the end. + _file_pointer = 0; + _real_file->seek(0, byte_filer::FROM_START); + size_t naive_size = _real_file->length(); + if (naive_size < _real_file->file_size_limit()) { + // lucked out; we are within normal file size limitations. + seek(save_posn, byte_filer::FROM_START); + return double(naive_size); + } + + double best_highest = 0.0; // the maximum we've safely seeked to. + + size_t big_jump = byte_filer::file_size_limit(); + // try with the largest possible seek at first. + + while (true) { +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf("best highest=%.0f", best_highest)); +#endif + // iterate until we reach our exit condition, which seems like it must + // always occur eventually unless the file is being monkeyed with. + bool seek_ret = _real_file->seek(int(big_jump), byte_filer::FROM_CURRENT); +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" seek ret=%d", int(seek_ret))); +#endif + byte_array temp_bytes; + int bytes_read = _real_file->read(temp_bytes, 1); + if (bytes_read < 1) + seek_ret = false; +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" read %d bytes", bytes_read)); +#endif + bool at_eof = _real_file->eof(); +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" at_eof=%d", int(at_eof))); +#endif + if (seek_ret && !at_eof) { +#ifdef DEBUG_HUGE_FILE + LOG("seek worked, incrementing best highest and trying same jump again"); +#endif + // the seek worked, so we'll just jump forward again. + best_highest += double(big_jump); + _file_pointer += double(big_jump); + continue; + } else if (seek_ret && at_eof) { +#ifdef DEBUG_HUGE_FILE + LOG("seek worked but found eof exactly."); +#endif + // the seek did worked, but apparently we've also found the end point. + best_highest += double(big_jump); + _file_pointer += double(big_jump); + break; + } else { + // that seek was too large, so we need to back down and try a smaller + // seek size. +#ifdef DEBUG_HUGE_FILE + LOG("seek failed, going back to best highest and trying same jump again"); +#endif + _file_pointer = 0; + _real_file->seek(0, byte_filer::FROM_START); + outcome worked = seek(best_highest, byte_filer::FROM_START); + // this uses our version to position at large sizes. + if (worked != OKAY) { + // this is a bad failure; it says that the file size changed or + // something malfunctioned. we should always be able to get back to + // the last good size we found if the file is static. + LOG(a_sprintf("failed to seek back to best highest %.0f on ", + best_highest) + _real_file->filename()); + // try to repair our ideas about the file by starting the process + // over. +//hmmm: count the number of times restarted and bail after N. + seek_ret = _real_file->seek(0, byte_filer::FROM_START); + _file_pointer = 0; + if (!seek_ret) { + // the heck with this. we can't even go back to the start. this + // file seems to be screwed up now. + LOG(astring("failed to seek back to start of file! on ") + + _real_file->filename()); + return 0; + } + // reset the rest of the positions for our failed attempt to return + // to what we already thought was good. + _file_pointer = 0; + big_jump = byte_filer::file_size_limit(); + best_highest = 0; + continue; + } + // okay, nothing bad happened when we went back to our last good point. + if (big_jump <= 0) { + // success in finding the smallest place that we can't seek between. +#ifdef DEBUG_HUGE_FILE + LOG("got down to smallest big jump, 0!"); +#endif + break; + } + // formula expects that the maximum file size is a power of 2. + big_jump /= 2; +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf("restraining big jump down to %u.", big_jump)); +#endif + continue; + } + } + + // go back to where we started out. + seek(0, byte_filer::FROM_START); + seek(save_posn, byte_filer::FROM_CURRENT); +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf("saying file len is %.0f.", best_highest + 1.0)); +#endif + return best_highest + 1.0; +} + +bool huge_file::good() const { return _real_file->good(); } + +bool huge_file::eof() const { return _real_file->eof(); } + +outcome huge_file::move_to(double absolute_posn) +{ +#ifdef DEBUG_HUGE_FILE + FUNCDEF("move_to"); +#endif + double difference = absolute_posn - _file_pointer; + // calculate the size we want to offset. +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf("abs_pos=%.0f difference=%.0f old_filepoint=%.0f", + absolute_posn, difference, _file_pointer)); +#endif + // if we're at the same place, we don't have to do anything. + if (difference < 0.000001) { +#ifdef DEBUG_HUGE_FILE + LOG("difference was minimal, saying we're done."); +#endif + return OKAY; + } + while (absolute_value(difference) > 0.000001) { + double seek_size = minimum(double(byte_filer::file_size_limit() - 1), + absolute_value(difference)); + if (difference < 0) + seek_size *= -1.0; // flip sign of seek. +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" seeksize=%d", int(seek_size))); +#endif + bool seek_ret = _real_file->seek(int(seek_size), + byte_filer::FROM_CURRENT); + if (!seek_ret) { +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf("failed to seek %d from current", int(seek_size))); +#endif + return FAILURE; // seek failed somehow. + } + _file_pointer += seek_size; +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" now_filepoint=%.0f", _file_pointer)); +#endif + difference = absolute_posn - _file_pointer; +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" now_difference=%.0f", difference)); +#endif + } + return OKAY; +} + +outcome huge_file::seek(double new_position, byte_filer::origins origin) +{ +#ifdef DEBUG_HUGE_FILE + FUNCDEF("seek"); +#endif + if (origin == byte_filer::FROM_CURRENT) { + return move_to(_file_pointer + new_position); + } else if (origin == byte_filer::FROM_START) { + _file_pointer = 0; + if (!_real_file->seek(0, byte_filer::FROM_START)) + return FAILURE; + return move_to(new_position); + } else if (origin == byte_filer::FROM_END) { +#ifdef DEBUG_HUGE_FILE + LOG("into precarious FROM_END case."); +#endif + double file_len = length(); // could take a scary long time possibly. +#ifdef DEBUG_HUGE_FILE + LOG(a_sprintf(" FROM_END got len %.0f.", file_len)); +#endif + _file_pointer = file_len; + // it's safe, although not efficient, for us to call the length() + // method here. our current version of length() uses the byte_filer's + // seek method directly and only FROM_CURRENT and FROM_START from this + // class's seek method. + _real_file->seek(0, byte_filer::FROM_END); + return move_to(_file_pointer - new_position); + } + // unknown origin. + return BAD_INPUT; +} + +outcome huge_file::read(byte_array &to_fill, int desired_size, int &size_read) +{ +// FUNCDEF("read"); + size_read = 0; + int ret = _real_file->read(to_fill, desired_size); + if (ret < 0) + return FAILURE; // couldn't read the bytes. + _file_pointer += double(size_read); + size_read = ret; + return OKAY; +} + +outcome huge_file::write(const byte_array &to_write, int &size_written) +{ +// FUNCDEF("write"); + size_written = 0; + int ret = _real_file->write(to_write); + if (ret < 0) + return FAILURE; // couldn't write the bytes. + _file_pointer += double(size_written); + size_written = ret; + return OKAY; +} + +} //namespace. + diff --git a/nucleus/library/filesystem/huge_file.h b/nucleus/library/filesystem/huge_file.h new file mode 100644 index 00000000..69a10347 --- /dev/null +++ b/nucleus/library/filesystem/huge_file.h @@ -0,0 +1,97 @@ +#ifndef HUGE_FILE_CLASS +#define HUGE_FILE_CLASS + +/*****************************************************************************\ +* * +* Name : huge_file * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_filer.h" + +#include +#include +#include +#include + +namespace filesystem { + +//! Supports reading and writing to very large files, > 4 gigabytes. +/*! + The standard file I/O functions only handle files up to 4 gigabytes. This + class extends the range to essentially unlimited sizes, as long as the + operating system can accurately do relative seeks and can read/write to + files of the size needed. +*/ + +class huge_file +{ +public: + huge_file(const basis::astring &filename, const basis::astring &permissions); + //!< opens "filename" for access, where it presumably is a very large file. + /*!< see byte filer for a description of the permissions. + */ + virtual ~huge_file(); + + DEFINE_CLASS_NAME("huge_file"); + + enum outcomes { + OKAY = basis::common::OKAY, + FAILURE = basis::common::FAILURE, + ACCESS_DENIED = basis::common::ACCESS_DENIED, + BAD_INPUT = basis::common::BAD_INPUT + }; + + bool good() const; + //!< reports if the file was opened successfully. + + bool eof() const; + //!< reports when the file pointer has reached the end of the file. + + double length(); + //!< expensive operation accesses the file to find length. + + double file_pointer() const { return _file_pointer; } + //!< returns where we currently are in the file. + + basis::outcome seek(double new_position, + byte_filer::origins origin = byte_filer::FROM_CURRENT); + //!< move the file pointer to "new_position" if possible. + /*!< the relative seek is the easiest type of seek to accomplish with a + huge file. the other types are also supported, but take a bit more to + implement. */ + + basis::outcome move_to(double absolute_posn); + //!< simpler seek just goes from current location to "absolute_posn". + + basis::outcome read(basis::byte_array &to_fill, int desired_size, int &size_read); + //!< reads "desired_size" into "to_fill" if possible. + /*!< "size_read" reports how many bytes were actually read. */ + + basis::outcome write(const basis::byte_array &to_write, int &size_written); + //!< stores the array "to_write" into the file. + /*!< "size_written" reports how many bytes got written. */ + + bool truncate(); + //!< truncates the file after the current position. + + void flush(); + //!< forces any pending writes to actually be saved to the file. + +private: + byte_filer *_real_file; //!< supports us but is subject to 4g limit. + double _file_pointer; //!< position in the file if OS is tracking us. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/filesystem/makefile b/nucleus/library/filesystem/makefile new file mode 100644 index 00000000..746acf03 --- /dev/null +++ b/nucleus/library/filesystem/makefile @@ -0,0 +1,11 @@ +include cpp/variables.def + +PROJECT = filesystem +TYPE = library +SOURCE = byte_filer.cpp directory.cpp directory_tree.cpp file_info.cpp \ + file_time.cpp filename.cpp filename_list.cpp filename_tree.cpp \ + heavy_file_ops.cpp huge_file.cpp +TARGETS = filesystem.lib + +include cpp/rules.def + diff --git a/nucleus/library/loggers/combo_logger.cpp b/nucleus/library/loggers/combo_logger.cpp new file mode 100644 index 00000000..591bde24 --- /dev/null +++ b/nucleus/library/loggers/combo_logger.cpp @@ -0,0 +1,86 @@ +/*****************************************************************************\ +* * +* Name : combo_logger +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "combo_logger.h" + +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif +#include +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace filesystem; +using namespace structures; +using namespace textual; + +namespace loggers { + +//const int REDUCE_FACTOR = 5; + // we whack this portion of the file every time we truncate. if it's set + // to 14, for example, then a 14th of the file is whacked every time whacking + // is needed. + +//const int MAXIMUM_BUFFER_SIZE = 140000; + // the maximum allowed chunk that can be copied from the old logfile + // to the current one. + +//int static_chaos() { return chaos().inclusive(0, 1280004); } + +combo_logger::combo_logger(const astring &filename, int limit, stream_choices target) +: file_logger(filename, limit), + console_logger(target) +{ +} + +void combo_logger::add_filter(int new_filter) +{ + file_logger::add_filter(new_filter); + console_logger::add_filter(new_filter); +} + +void combo_logger::remove_filter(int old_filter) +{ + file_logger::remove_filter(old_filter); + console_logger::remove_filter(old_filter); +} + +void combo_logger::clear_filters() +{ + file_logger::clear_filters(); + console_logger::clear_filters(); +} + +void combo_logger::eol(textual::parser_bits::line_ending to_set) +{ + file_logger::eol(to_set); + console_logger::eol(to_set); +} + +outcome combo_logger::log(const base_string &info, int filter) +{ + console_logger::log(info, filter); + return file_logger::log(info, filter); +} + +} //namespace. + diff --git a/nucleus/library/loggers/combo_logger.h b/nucleus/library/loggers/combo_logger.h new file mode 100644 index 00000000..a593cf4e --- /dev/null +++ b/nucleus/library/loggers/combo_logger.h @@ -0,0 +1,59 @@ +#ifndef COMBO_LOGGER_CLASS +#define COMBO_LOGGER_CLASS + +/*****************************************************************************\ +* * +* Name : combo_logger +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "file_logger.h" + +namespace loggers { + +//! combines a file_logger with a console logger, behaving like the 'tee' command. +/*! this will output the diagnostic info to both a file and to the console. this is useful +when one would like to see the output as its happening but also have a record for later. */ + +class combo_logger : public virtual file_logger, public virtual console_logger +{ +public: + combo_logger(const basis::astring &filename, + int limit = DEFAULT_LOG_FILE_SIZE, + stream_choices log_target = TO_STDOUT); + + virtual ~combo_logger() {} + + DEFINE_CLASS_NAME("combo_logger"); + + virtual basis::outcome log(const basis::base_string &info, int filter = basis::ALWAYS_PRINT); + + // overrides that enforce properties for both loggers. + virtual void add_filter(int new_filter); + virtual void remove_filter(int old_filter); + virtual void clear_filters(); + virtual void eol(textual::parser_bits::line_ending to_set); +}; + +////////////// + +//! a macro that retasks the program-wide logger as a combo_logger. +#define SETUP_COMBO_LOGGER { \ + basis::base_logger *old_log = program_wide_logger::set \ + (new loggers::combo_logger \ + (loggers::file_logger::log_file_for_app_name())); \ + WHACK(old_log); \ +} + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/console_logger.cpp b/nucleus/library/loggers/console_logger.cpp new file mode 100644 index 00000000..aeb304b0 --- /dev/null +++ b/nucleus/library/loggers/console_logger.cpp @@ -0,0 +1,63 @@ +/*****************************************************************************\ +* * +* Name : console_logger * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "console_logger.h" +#include "logging_filters.h" + +#include + +using namespace basis; + +namespace loggers { + +console_logger::console_logger(stream_choices target) : c_target(target) +{} + +console_logger::~console_logger() {} + +outcome console_logger::log(const base_string &info, int filter) +{ + +if (filter) {} //temp ignored + + FILE *log_to = stdout; + if (c_target == TO_STDERR) log_to = stderr; + +//hmmm: temp simplified form during bootup of new hoople. +fprintf(log_to, "%s\n", (char *)info.observe()); + +/* +hmmm: need filter set support! + if (member(filter)) { +*/ + // format the output with %s to ensure we get all characters, rather + // than having some get interpreted if we used info as the format spec. +// fprintf(log_to, "%s", (char *)info.s()); + // send the EOL char if the style is appropriate for that. +// if (eol() != NO_ENDING) fprintf(log_to, "%s", get_ending().s()); + + + // write immediately to avoid lost output on crash. + fflush(log_to); + +/* +hmmm: need filter set support! + } +*/ + return common::OKAY; +} + +} //namespace. + + diff --git a/nucleus/library/loggers/console_logger.h b/nucleus/library/loggers/console_logger.h new file mode 100644 index 00000000..c1984df8 --- /dev/null +++ b/nucleus/library/loggers/console_logger.h @@ -0,0 +1,79 @@ +#ifndef CONSOLE_LOGGER_CLASS +#define CONSOLE_LOGGER_CLASS + +/*****************************************************************************\ +* * +* Name : console_logger * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! A logger that sends to the console screen using the standard output device. +/*! + The object can optionally be used to log to the standard error device + instead. +*/ + +#include "standard_log_base.h" + +#include +#include +#include + +namespace loggers { + +class console_logger : public virtual standard_log_base +{ +public: + enum stream_choices { TO_STDOUT, TO_STDERR }; //!< where to send the logging. + + console_logger(stream_choices log_target = TO_STDOUT); + //!< if "standard_error" is true, than stderr is used instead of stdout. + + virtual ~console_logger(); + + DEFINE_CLASS_NAME("console_logger"); + + bool to_standard_output() const { return c_target == TO_STDOUT; } + //!< reports if the logger goes to standard output (default). + + bool to_standard_error() const { return c_target == TO_STDERR; } + //!< reports if the logger goes to standard error instead. + + void set_output(stream_choices target) { c_target = target; } + //!< enables the target of logging to be changed after construction. + + virtual basis::outcome log(const basis::base_string &info, int filter); + //!< sends the string "info" to the standard output device. + /*!< if the "filter" is not in the current filter set, then the + information will be dropped. otherwise the information is displayed, + where appropriate. for some environments, this will essentially throw + it away. for example, in ms-windows, a windowed program will only save + standard out if one redirects standard output to a file, whereas a + console mode program will output the text to its parent prompt. */ + +private: + stream_choices c_target; //!< records whether stdout or stderr is used. +}; + +////////////// + +//!< a macro that retasks the program-wide logger as a console_logger. +#define SETUP_CONSOLE_LOGGER { \ + loggers::standard_log_base *old_log = loggers::program_wide_logger::set \ + (new loggers::console_logger); \ + /* assumes we are good to entirely remove the old logger. */ \ + WHACK(old_log); \ +} + +} // namespace. + +#endif + diff --git a/nucleus/library/loggers/critical_events.cpp b/nucleus/library/loggers/critical_events.cpp new file mode 100644 index 00000000..ebb60bb8 --- /dev/null +++ b/nucleus/library/loggers/critical_events.cpp @@ -0,0 +1,284 @@ +/*****************************************************************************\ +* * +* Name : critical_events * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "critical_events.h" +#include "program_wide_logger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace structures; +using namespace configuration; +using namespace textual; +using namespace timely; + +const int MESSAGE_SPACE_PROVIDED = 4096; + // the strings should not be larger than this for diagnostic / error messages. + +namespace loggers { + +SAFE_STATIC(astring, critical_events::hidden_critical_events_dir, ) + // define the function that holds the directory string. + +astring default_critical_location() +{ + // ensure that the critical events function logs to the appropriate place. + return application_configuration::get_logging_directory(); +} + +SAFE_STATIC(mutex, __critical_event_dir_lock, ) + +basis::un_int critical_events::system_error() +{ +#if defined(__UNIX__) + return errno; +#elif defined(__WIN32__) + return GetLastError(); +#else + #pragma error("hmmm: no code for error number for this operating system") + return 0; +#endif +} + +astring critical_events::system_error_text(basis::un_int to_name) +{ +#if defined(__UNIX__) + return strerror(to_name); +#elif defined(__WIN32__) + char error_text[1000]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NIL, to_name, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)error_text, + sizeof(error_text) - 1, NIL); + astring to_return = error_text; + // trim off the ridiculous carriage return they add. + while ( (to_return[to_return.end()] == '\r') + || (to_return[to_return.end()] == '\n') ) + to_return.zap(to_return.end(), to_return.end()); + return to_return; +#else + #pragma error("hmmm: no code for error text for this operating system") + return ""; +#endif +} + +astring critical_events::critical_events_directory() +{ + static bool initted = false; + if (!initted) { + auto_synchronizer l(__critical_event_dir_lock()); + if (!initted) { + set_critical_events_directory(default_critical_location()); + initted = true; + } + } + return hidden_critical_events_dir(); +} + +void critical_events::set_critical_events_directory(const astring &directory) +{ hidden_critical_events_dir() = directory; } + +void critical_events::write_to_critical_events(const char *to_write) +{ + astring filename = critical_events_directory(); + filename += "/runtime_issues.log"; + FILE *errfile = fopen(filename.s(), "ab"); + if (errfile) { + astring app_name = application_configuration::application_name(); + int indy = app_name.find('/', app_name.end(), true); + if (non_negative(indy)) app_name.zap(0, indy); + indy = app_name.find('\\', app_name.end(), true); + if (non_negative(indy)) app_name.zap(0, indy); + fprintf(errfile, "%s [%s]:%s", time_stamp::notarize(true).s(), + app_name.s(), parser_bits::platform_eol_to_chars()); + fprintf(errfile, "%s%s", to_write, parser_bits::platform_eol_to_chars()); + fclose(errfile); + } +} + +void critical_events::write_to_console(const char *guards_message_space) +{ fprintf(stderr, "%s", (char *)guards_message_space); fflush(stderr); } + +void critical_events::alert_message(const char *info, const char *title) +{ + astring to_print; + if (strlen(title)) { + const char border = '='; + to_print += astring(border, int(strlen(title)) + 4); + to_print += parser_bits::platform_eol_to_chars(); + to_print += border; + to_print += ' '; + to_print += title; + to_print += ' '; + to_print += border; + to_print += parser_bits::platform_eol_to_chars(); + to_print += astring(border, int(strlen(title)) + 4); + to_print += parser_bits::platform_eol_to_chars(); + } + + to_print += info; + program_wide_logger::get().log(to_print, ALWAYS_PRINT); + fflush(NIL); // flush all output streams. +} + +void critical_events::alert_message(const astring &info) { alert_message(info.s()); } + +void critical_events::alert_message(const astring &info, const astring &title) +{ alert_message(info.s(), title.s()); } + +void critical_events::make_error_message(const char *file, int line, + const char *error_class, const char *error_function, + const char *info, char *guards_message_space) +{ + strcpy(guards_message_space, "\nProblem reported for \""); +//hmmm: only copy N chars of each into the space. +// say 40 for class/function each, then the space - consumed for the +// info. get strlen for real on the class and function name to know +// actual size for that computation. + strcat(guards_message_space, error_class); + strcat(guards_message_space, "::"); + strcat(guards_message_space, error_function); + strcat(guards_message_space, "\"\n(invoked at line "); + char line_num[20]; + sprintf(line_num, "%d", line); + strcat(guards_message_space, line_num); + strcat(guards_message_space, " in "); + strcat(guards_message_space, file); + strcat(guards_message_space, " at "); + strcat(guards_message_space, time_stamp::notarize(false).s()); + strcat(guards_message_space, ")\n"); + strcat(guards_message_space, info); + strcat(guards_message_space, "\n\n\n"); +} + +void critical_events::FL_deadly_error(const char *file, int line, const char *error_class, + const char *error_function, const char *info) +{ + FL_continuable_error(file, line, error_class, error_function, info, + "Deadly Error Information"); + CAUSE_BREAKPOINT; + throw "deadly_error"; + // abort() is not as good an approach as throwing an exception. aborts are + // harder to track with some compilers, but all should be able to trap an + // exception. +} + +void critical_events::FL_deadly_error(const astring &file, int line, + const astring &error_class, const astring &error_function, + const astring &info) +{ + FL_deadly_error(file.s(), line, error_class.s(), error_function.s(), + info.s()); +} + +void critical_events::FL_continuable_error_real(const char *file, int line, + const char *error_class, const char *error_function, const char *info, + const char *title) +{ + char guards_message_space[MESSAGE_SPACE_PROVIDED]; + // this routine could still fail, if the call stack is already jammed + // against its barrier. but if that's the case, even the simple function + // call might fail. in any case, we cannot deal with a stack overflow + // type error using this function. but we would rather deal with that + // than make the space static since that would cause corruption when + // two threads wrote errors at the same time. + make_error_message(file, line, error_class, error_function, info, + guards_message_space); + alert_message(guards_message_space, title); +} + +void critical_events::FL_continuable_error(const char *file, int line, + const char *error_class, const char *error_function, const char *info, + const char *title) +{ + FL_continuable_error_real(file, line, error_class, error_function, info, title); +} + +void critical_events::FL_continuable_error(const astring &file, int line, + const astring &error_class, const astring &error_function, + const astring &info, const astring &title) +{ + FL_continuable_error_real(file.s(), line, error_class.s(), + error_function.s(), info.s(), title.s()); +} + +void critical_events::FL_non_continuable_error(const char *file, int line, + const char *error_class, const char *error_function, const char *info, + const char *title) +{ + FL_continuable_error_real(file, line, error_class, error_function, info, + title); + exit(EXIT_FAILURE); // let the outside world know that there was a problem. +} + +void critical_events::FL_non_continuable_error(const astring &file, int line, + const astring &error_class, const astring &error_function, + const astring &info, const astring &title) +{ + FL_continuable_error_real(file.s(), line, error_class.s(), + error_function.s(), info.s(), title.s()); + exit(EXIT_FAILURE); // let the outside world know that there was a problem. +} + +void critical_events::FL_console_error(const char *file, int line, const char *error_class, + const char *error_function, const char *info) +{ + char guards_message_space[MESSAGE_SPACE_PROVIDED]; + make_error_message(file, line, error_class, error_function, info, + guards_message_space); + write_to_console(guards_message_space); +} + +void critical_events::FL_out_of_memory_now(const char *file, int line, + const char *the_class_name, const char *the_func) +{ + FL_non_continuable_error(file, line, the_class_name, the_func, + "Program stopped due to memory allocation failure.", + "Out of Memory Now"); +} + +void critical_events::implement_bounds_halt(const char *the_class_name, const char *the_func, + const char *value, const char *low, const char *high, + const char *error_addition) +{ + char message[400]; + strcpy(message, "bounds error caught"); + strcat(message, error_addition); + strcat(message, ":\r\n"); + strcat(message, value); + strcat(message, " is not between "); + strcat(message, low); + strcat(message, " and "); + strcat(message, high); +#ifdef ERRORS_ARE_FATAL + deadly_error(the_class_name, the_func, message); +#else + continuable_error(the_class_name, the_func, message); +#endif +} + +} //namespace. + diff --git a/nucleus/library/loggers/critical_events.h b/nucleus/library/loggers/critical_events.h new file mode 100644 index 00000000..f814de50 --- /dev/null +++ b/nucleus/library/loggers/critical_events.h @@ -0,0 +1,205 @@ +#ifndef CRITICAL_EVENTS_GROUP +#define CRITICAL_EVENTS_GROUP + +/*****************************************************************************\ +* * +* Name : critical_events * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace loggers { + +//! This macro wraps the notion of stopping in the debugger. +#ifdef __UNIX__ + #define CAUSE_BREAKPOINT +//hmmm: need a unix equivalent for this. supporting gcc might be enough. +#elif defined(__WIN32__) + #ifdef __MINGW32__ + extern "C" { +// #include +//for debugbreak. does this cause other problems? + } + #define CAUSE_BREAKPOINT +// #define CAUSE_BREAKPOINT __debugbreak() + #else + #define CAUSE_BREAKPOINT __debugbreak() + #endif +#endif + +//! Tests the value "check" to ensure that it's not zero. +/*! This can be used instead of an ASSERT macro to check conditions in +builds with ERRORS_ARE_FATAL turned on. This macro is orthogonal to the +build being built with debugging or release features. */ +#ifdef ERRORS_ARE_FATAL + #define REQUIRE(check) if (!check) CAUSE_BREAKPOINT; +#else + #define REQUIRE(check) +#endif + +// now more dangerous or potent guards... + +//! an extra piece of information used, if available, in bounds_halt below. +//#define BH_ERROR_ADDITION ((basis::astring(" in ") + _global_argv[0]).s()) + +//! Verifies that "value" is between "low" and "high", inclusive. +/*! "Value" must be an object for which greater than and less than are defined. +The static_class_name() method and func definition are used to tag the +complaint that is emitted when problems are detected. Note that if +CATCH_ERRORS is defined, then the program is _halted_ if the value is out +of bounds. Otherwise, the "to_return" value is returned. */ +//#ifdef CATCH_ERRORS +// #define bounds_halt(value, low, high, to_return) { +// if (((value) < (low)) || ((value) > (high))) { +// critical_events::implement_bounds_halt(static_class_name(), func, #value, #low, +// #high, BH_ERROR_ADDITION); +// return to_return; +// } +// } +//#else +// #define bounds_halt(a, b, c, d) bounds_return(a, b, c, d) +//#endif + +////////////// + +//! Provide some macros that will automatically add the file and line number. +/*! These use the functions below to report different types of error +situations and in some cases, exit the program. */ +#define non_continuable_error(c, f, i) \ + critical_events::FL_non_continuable_error(__FILE__, __LINE__, c, f, i, \ + "A Non-Continuable Runtime Problem Has Occurred") +#define continuable_error(c, f, i) \ + critical_events::FL_continuable_error(__FILE__, __LINE__, c, f, i, \ + "Runtime Problem Information") +#define console_error(c, f, i) \ + critical_events::FL_console_error(__FILE__, __LINE__, c, f, i) +#define deadly_error(c, f, i) \ + critical_events::FL_deadly_error(__FILE__, __LINE__, c, f, i) +#define out_of_memory_now(c, f) \ + critical_events::FL_out_of_memory_now(__FILE__, __LINE__, c, f) + +////////////// + +//! Provides a means of logging events for runtime problems. + +class critical_events : public virtual basis::root_object +{ +public: + virtual ~critical_events() {} + + // super handy system inter-operation functions... + + static basis::un_int system_error(); + //!< gets the most recent system error reported on this thread. + + static basis::astring system_error_text(basis::un_int error_to_show); + //!< returns the OS's string form of the "error_to_show". + /*!< this often comes from the value reported by system_error(). */ + + // some noisemaking methods... + + //! Prints out a message to the standard error file stream. + static void write_to_console(const char *message); + + //! shows the message in "info", with an optional "title" on the message. + /*! the message is sent to the program wide logger, if one is expected to + exist in this program. */ + static void alert_message(const char *info, const char *title = "Alert Message"); + static void alert_message(const basis::astring &info); // uses default title. + static void alert_message(const basis::astring &info, const basis::astring &title); // use "title". + + static void write_to_critical_events(const char *message); + //!< sends the "message" to the critical events log file. + /*!< Prints out a message to the specially named file that captures all + serious events written to error logging functions. If you use the + functions in this file, you are likely already writing to this log. */ + + static void set_critical_events_directory(const basis::astring &directory); + //!< sets the internal location where the critical events will be logged. + /*!< this is postponed to a higher level, although the default + will work too. */ + + static basis::astring critical_events_directory(); + //!< returns the current location where critical events are written. + + // this section implements the error macros. + + //! Prints out an error message and then exits the program. + /*! The parameters describe the location where the error was detected. This + call should only be used in test programs or where absolutely necessary + because it causes an almost immediate exit from the program! deadly_error + should be reserved for when the program absolutely has to exit right then, + because this function will actually enforce a crash / abort / break into + debugger action rather than just calling exit(). */ + static void FL_deadly_error(const char *file, int line, const char *classname, + const char *function_name, const char *info); + //! A version that takes strings instead of char pointers. + static void FL_deadly_error(const basis::astring &file, int line, + const basis::astring &classname, const basis::astring &function_name, + const basis::astring &info); + + //! Describes an error like deadly_error, but does not exit the program. + static void FL_continuable_error(const char *file, int line, + const char *classname, const char *function_name, const char *info, + const char *title); + + //! A version using astring instead of char pointers. + static void FL_continuable_error(const basis::astring &file, int line, + const basis::astring &classname, const basis::astring &function_name, + const basis::astring &info, const basis::astring &title); + + //! Shows the same information as continuable_error, but causes an exit. + /*! This is a friendlier program exit than deadly_error. This version can + be used when the program must stop, possibly because of a precondition failure + or a problem in input data or basically whatever. it is not intended to + abort or break into the debugger, but to simply exit(). */ + static void FL_non_continuable_error(const char *file, int line, + const char *classname, const char *function_name, const char *info, + const char *title); + + //! A version using astring instead of char pointers. + static void FL_non_continuable_error(const basis::astring &file, int line, + const basis::astring &classname, const basis::astring &function_name, + const basis::astring &info, const basis::astring &title); + + //! Causes the program to exit due to a memory allocation failure. + static void FL_out_of_memory_now(const char *file, int line, + const char *classname, const char *function_name); + + //! Prints out an error message to the standard error file stream. + static void FL_console_error(const char *file, int line, const char *error_class, + const char *error_function, const char *info); + + //! Used to build our particular type of error message. + /*! It writes an error message into "guards_message_space" using our stylized + object::method formatting. */ + static void make_error_message(const char *file, int line, + const char *error_class, const char *error_function, + const char *info, char *guards_message_space); + + //! Provides the real implementation of bounds_halt to save code space. + static void implement_bounds_halt(const char *the_class_name, const char *func, + const char *value, const char *low, const char *high, + const char *error_addition); + +private: + static basis::astring &hidden_critical_events_dir(); + + static void FL_continuable_error_real(const char *file, int line, + const char *error_class, const char *error_function, const char *info, + const char *title); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/definitions_wx.h b/nucleus/library/loggers/definitions_wx.h new file mode 100644 index 00000000..9a4c29e7 --- /dev/null +++ b/nucleus/library/loggers/definitions_wx.h @@ -0,0 +1,44 @@ +#ifndef WX_DEFINITIONS_GROUP +#define WX_DEFINITIONS_GROUP + +/*****************************************************************************\ +* * +* Name : definitions for wx * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Some macros that support the use of WX widgets. * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +#include + +namespace loggers { + +#ifdef wxUSE_UNICODE + //! converts to wx's UTF-16 string format. + #define to_unicode_wx(s) ((const wxChar *)(const flexichar *)transcode_to_utf16(s)) + //! converts a wx UTF-16 string to UTF-8. + #define from_unicode_wx(s) ((const char *)(const UTF8 *)transcode_to_utf8(s)) +#else + // turn off the unicode macro to ensure nothing gets confused. + #undef UNICODE + // placeholder macros try not to do much. + #define to_unicode_wx(s) ((const wxChar *)null_transcoder(s, false)) + #define from_unicode_wx(s) ((const char *)null_transcoder((const wxChar *)s, false)) +#endif + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/eol_aware.h b/nucleus/library/loggers/eol_aware.h new file mode 100644 index 00000000..9e6280f8 --- /dev/null +++ b/nucleus/library/loggers/eol_aware.h @@ -0,0 +1,53 @@ +#ifndef EOL_AWARE_CLASS +#define EOL_AWARE_CLASS + +/*****************************************************************************\ +* * +* Name : eol_aware +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace loggers { + +//! Provides an abstract base for logging mechanisms. +/*! + This assists greatly in generating diagnostic entries polymorphically and in + enabling different logging mechanisms to be plugged in easily. Note that all + of the functions are defined virtually which enables overriding pretty much + all of the base functionality. Use this wisely, if ever. +*/ + +class eol_aware : public virtual basis::root_object +{ +public: + virtual textual::parser_bits::line_ending eol() { return c_ending; } + //!< observes how line endings are to be printed. + + virtual void eol(textual::parser_bits::line_ending to_set) { c_ending = to_set; } + //!< modifies how line endings are to be printed. + + virtual basis::astring get_ending() { return textual::parser_bits::eol_to_chars(c_ending); } + //!< returns a string for the current ending. + + virtual void get_ending(basis::astring &to_end) { to_end = textual::parser_bits::eol_to_chars(c_ending); } + //!< appends a string for the current ending to "to_end". + +private: + textual::parser_bits::line_ending c_ending; //!< the current printing style. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/file_logger.cpp b/nucleus/library/loggers/file_logger.cpp new file mode 100644 index 00000000..a18caa14 --- /dev/null +++ b/nucleus/library/loggers/file_logger.cpp @@ -0,0 +1,375 @@ +/*****************************************************************************\ +* * +* Name : file_logger * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "critical_events.h" +#include "file_logger.h" +#include "logging_filters.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif +#include +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace mathematics; +using namespace structures; +using namespace textual; + +namespace loggers { + +const int REDUCE_FACTOR = 5; + // we whack this portion of the file every time we truncate. if it's set + // to 14, for example, then a 14th of the file is whacked every time whacking + // is needed. + +const int MAXIMUM_BUFFER_SIZE = 140000; + // the maximum allowed chunk that can be copied from the old logfile + // to the current one. + +int static_chaos() { + static chaos __hidden_chaos; + return __hidden_chaos.inclusive(0, 1280004); +} + +file_logger::file_logger() +: _filename(new astring()), + _file_limit(DEFAULT_LOG_FILE_SIZE), + _outfile(NIL), + _flock(new mutex) +{ + name(""); +} + +file_logger::file_logger(const astring &initial_filename, int limit) +: _filename(new astring()), + _file_limit(limit), + _outfile(NIL), + _flock(new mutex) +{ + name(initial_filename); + // we don't open the file right away because we don't know they'll ever + // use the thing. +} + +file_logger::~file_logger() +{ + close_file(); + WHACK(_filename); + WHACK(_flock); +} + +basis::astring file_logger::log_file_for_app_name() +{ + filename prog = application_configuration::application_name(); + return application_configuration::make_logfile_name(prog.rootname() + ".log"); +} + +bool file_logger::reopen() +{ + auto_synchronizer l(*_flock); + name(*_filename); + return open_file(); +} + +void file_logger::close_file() +{ + auto_synchronizer l(*_flock); + if (_outfile) _outfile->flush(); + // dump anything that hasn't gone out yet. + WHACK(_outfile); +} + +void file_logger::name(const astring &new_name) +{ + auto_synchronizer l(*_flock); + close_file(); + *_filename = new_name; +} + +int file_logger::size_reduction() const +{ + auto_synchronizer l(*_flock); + return int(_file_limit / REDUCE_FACTOR); +} + +bool file_logger::good() const +{ + auto_synchronizer l(*_flock); + if (!_outfile && !_file_limit) return true; + if (!_outfile) return false; + return _outfile->good(); +} + +astring file_logger::name() const +{ + auto_synchronizer l(*_flock); + return *_filename; +} + +void file_logger::flush() +{ + auto_synchronizer l(*_flock); + if (!_outfile) open_file(); + if (_outfile) _outfile->flush(); +} + +bool file_logger::open_file() +{ + auto_synchronizer l(*_flock); + close_file(); // close any existing log file. + + if (!_file_limit) { + // if there's a limit of zero, we'll never open the file. + return true; + } + + // make sure we've got a name. + if (!*_filename) { + // if the name is empty, they don't want to save to a normal log file. + _outfile = new byte_filer; + return true; + } + + // canonicalize the name so that we use the same tag for synchronization. + // this might still fail if there are some jokers using different relative + // paths to the file. but if it's an absolute name, it should work. + for (int i = 0; i < _filename->length(); i++) + if ((*_filename)[i] == '\\') (*_filename)[i] = '/'; + + // make sure the directory containing the log file exists, if we can. + filename temp_file(*_filename); + filename temp_dir(temp_file.dirname()); + if (!temp_dir.good() || !temp_dir.is_directory()) { + directory::recursive_create(temp_dir); + } + + // if this opening doesn't work, then we just can't log. + _outfile = new byte_filer(*_filename, "a+b"); + return _outfile->good(); +} + +outcome file_logger::log(const base_string &to_show, int filter) +{ + if (!_file_limit) return common::OKAY; + + size_t current_size = 0; + { + auto_synchronizer l(*_flock); + if (!member(filter)) return common::OKAY; + if (!_outfile) open_file(); + if (!_outfile) return common::BAD_INPUT; // file opening failed. + if (!_outfile->good()) return common::BAD_INPUT; + // there is no log file currently. + + // dump the string out. + if (to_show.length()) + _outfile->write((abyte *)to_show.observe(), to_show.length()); +//hmmm: need eol feature again. +// if (eol() != NO_ENDING) { +// astring end = get_ending(); +astring end = parser_bits::platform_eol_to_chars(); + _outfile->write((abyte *)end.s(), end.length()); +// } + current_size = _outfile->tell(); + flush(); + } + + // check if we need to truncate yet. + if (current_size > _file_limit) truncate(_file_limit - size_reduction()); + return common::OKAY; +} + +outcome file_logger::log_bytes(const byte_array &to_log, int filter) +{ + if (!_file_limit) return common::OKAY; + + size_t current_size = 0; + { + auto_synchronizer l(*_flock); + if (!member(filter)) return common::OKAY; + if (!_outfile) open_file(); + if (!_outfile) return common::BAD_INPUT; // file opening failed. + if (!_outfile->good()) return common::BAD_INPUT; + // there is no log file currently. + + // dump the contents out. + if (to_log.length()) + _outfile->write(to_log.observe(), to_log.length()); + current_size = _outfile->tell(); + flush(); + } + + // check if we need to truncate yet. + if (current_size > _file_limit) + truncate(_file_limit - size_reduction()); + return common::OKAY; +} + +outcome file_logger::format_bytes(const byte_array &to_log, int filter) +{ + if (!_file_limit) return common::OKAY; + + { + auto_synchronizer l(*_flock); + if (!member(filter)) return common::OKAY; + if (!_outfile) open_file(); + if (!_outfile) return common::BAD_INPUT; // file opening failed. + if (!_outfile->good()) return common::BAD_INPUT; + // there is no log file currently. + } + + // dump the contents out. + if (to_log.length()) { + astring dumped_form; + byte_formatter::text_dump(dumped_form, to_log); + log(dumped_form); + } + + // check if we need to truncate yet. +// int current_size = _outfile->tell(); +// flush(); +// if (current_size > _file_limit) truncate(_file_limit - size_reduction()); + + return common::OKAY; +} + +//hmmm: should move the truncation functionality into a function on +// the file object. + +void file_logger::truncate(size_t new_size) +{ + auto_synchronizer l(*_flock); + if (!_outfile) open_file(); + if (!_outfile) return; // file opening failed. + if (!_outfile->good()) return; + // there is no log file currently. + + size_t current_size = 0; + +/// // our synchronization scheme allows us to use this inter-application +/// // lock; the logger's own lock is always acquired first. no one else can +/// // grab the "file_lock", so no deadlocks. +/// +/// rendezvous file_lock(*_filename + "_trunclock"); +/// if (!file_lock.healthy()) { +/// critical_events::write_to_critical_events((astring("could not create " +/// "lock for ") + *_filename).s()); +/// return; +/// } +/// // waiting forever until the file lock succeeds. as long as there are +/// // no deadlocks permitted, then this shouldn't be too dangerous... +/// bool got_lock = file_lock.lock(rendezvous::ENDLESS_WAIT); +/// if (!got_lock) { +/// critical_events::write_to_critical_events((astring("could not acquire " +/// "lock for ") + *_filename).s()); +/// return; +/// } + + // make sure we weren't second in line to clean the file. if someone else + // already did this, we don't need to do it again. + _outfile->seek(0, byte_filer::FROM_END); + current_size = _outfile->tell(); + if (current_size <= new_size) { + // release the lock and leave since we don't need to change the file. +/// file_lock.unlock(); + return; + } + // create a bogus temporary name. + astring new_file(astring::SPRINTF, "%s.tmp.%d", name().s(), + static_chaos()); + + // unlink the temp file, if it exists. + unlink(new_file.s()); + + // grab the current size before we close our file. + current_size = _outfile->tell(); + + // redo the main stream for reading also. + WHACK(_outfile); + _outfile = new byte_filer(*_filename, "rb"); + + // open the temp file as blank for writing. + byte_filer *hold_stream = new byte_filer(new_file, "w+b"); + + int start_of_keep = int(current_size - new_size); + + // position the old file where it will be about the right size. + _outfile->seek(start_of_keep, byte_filer::FROM_START); + + astring buff(' ', MAXIMUM_BUFFER_SIZE + 1); + + // we only read as long as the file end isn't hit and we're not past the + // original end of the file. if the file got bigger during the truncation, + // that's definitely not our doing and should not be coddled. we've seen + // a situation where we never thought we'd hit the end of the file yet before + // adding this size check. + size_t bytes_written = 0; // how many bytes have gone out already. +//hmmm: loop could be extracted to some kind of dump from file one into file +// two operation that starts at a particular address and has a particular +// size or range. + while (!_outfile->eof() && (bytes_written <= new_size)) { + // grab a line from the input file. + buff[0] = '\0'; // set it to be an empty string. + int bytes_read = _outfile->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE); + if (!bytes_read) + break; + bytes_written += bytes_read; + // write the line and a CR to the output file. + if (!_outfile->eof() || bytes_read) + hold_stream->write((abyte *)buff.s(), bytes_read); + } + WHACK(_outfile); + _outfile = new byte_filer(*_filename, "w+b"); + + // we think the new stream is ready for writing. + size_t hold_size = hold_stream->tell(); + // get the current length of the clipped chunk. + bytes_written = 0; // reset our counter. + + // jump back to the beginning of the temp file. + hold_stream->seek(0, byte_filer::FROM_START); + while (!hold_stream->eof() && (bytes_written <= hold_size) ) { + // scoot all the old data back into our file. + int bytes_read = hold_stream->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE); + if (!bytes_read) + break; + // something funky happened; we shouldn't be at the end of the file yet. + bytes_written += bytes_read; + if (!hold_stream->eof() || bytes_read) + _outfile->write((abyte *)buff.s(), bytes_read); + } + WHACK(hold_stream); + unlink(new_file.s()); // trash the temp file. +/// file_lock.unlock(); // repeal the process-wide lock. + name(*_filename); // re-open the regular file with append semantics. +} + +} //namespace. + diff --git a/nucleus/library/loggers/file_logger.h b/nucleus/library/loggers/file_logger.h new file mode 100644 index 00000000..3fc0319b --- /dev/null +++ b/nucleus/library/loggers/file_logger.h @@ -0,0 +1,139 @@ +#ifndef FILE_LOGGER_CLASS +#define FILE_LOGGER_CLASS + +/*****************************************************************************\ +* * +* Name : file_logger * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//! Enables the printing of information to a log file. +/*! + The information can be conditionally printed using the filter support. + The log file will automatically be truncated when it passes the size limit. +*/ + +#include "console_logger.h" +#include "eol_aware.h" +#include "filter_set.h" + +#include +#include +#include +#include +#include +#include + +namespace loggers { + +class file_logger : public virtual standard_log_base +{ +public: + file_logger(); + //!< creates a logger without a log file and with the default size limit. + /*!< the log file name can be changed using filename(). */ + + file_logger(const basis::astring &filename, int limit = DEFAULT_LOG_FILE_SIZE); + //!< constructs a logger using the "filename" for output. + /*!< there will be no logging if the "filename" is empty. the "limit" + specifies how large the log file can be (in bytes). */ + + virtual ~file_logger(); + + DEFINE_CLASS_NAME("file_logger"); + + enum limits { + //! this just defines the default for the log file size. + DEFAULT_LOG_FILE_SIZE = 0x10F00D + }; + + bool good() const; + //!< returns true if the logger appears correctly hooked up to a file. + /*!< note that we don't open the file when file_logger is constructed; + it is only opened once the first logging is attempted. */ + + bool reopen(); + //!< closes the current file and attempts to reopen it. + /*!< this is handy if the original opening of the file failed. */ + + basis::outcome log(const basis::base_string &info, int filter = basis::ALWAYS_PRINT); + //!< writes information to the log file (if the filename is valid). + /*!< the "filter" value is checked to see if it is in the current set + of allowed filters. a value of zero is always printed. if the filename() + has not been set, then the information is lost. */ + + basis::outcome log_bytes(const basis::byte_array &to_log, int filter = basis::ALWAYS_PRINT); + //!< sends a stream of bytes "to_log" without interpretation into the log. + /*!< if the "filter" is not enabled, then the info is just tossed out. */ + + basis::outcome format_bytes(const basis::byte_array &to_log, int filter = basis::ALWAYS_PRINT); + //!< fancifully formats a stream of bytes "to_log" and sends them into log. + + basis::astring name() const; + //!< observes the filename where logged information is written. + void name(const basis::astring &new_name); + //!< modifies the filename where logged information will be written. + /*!< if "new_name" is blank, then the logged information will not + be saved. */ + + int limit() const { return int(_file_limit); } + //!< observes the allowable size of the log file. + void limit(int new_limit) { _file_limit = new_limit; } + //!< modifies the allowable size of the log file. + + void flush(); + //!< causes any pending writes to be sent to the output file. + + void truncate(size_t new_size); + //!< chops the file to ensure it doesn't go much over the file size limit. + /*!< this can be used externally also, but be careful with it. */ + + //! returns a log file name for file_logger based on the program name. + /*! for a program named myapp.exe, this will be in the form: + {logging_dir}/myapp.log + */ + static basis::astring log_file_for_app_name(); + +private: + basis::astring *_filename; //!< debugging output file. + size_t _file_limit; //!< maximum length of file before truncation. + filesystem::byte_filer *_outfile; //!< the object that points at our output file. + basis::mutex *_flock; //!< protects the file and other parameters. + + int size_reduction() const; + //!< returns the size of the chunk to truncate from the file. + /*!< this is a percentage of the maximum size allowed. */ + + bool open_file(); + //!< if the opening of the file is successful, then true is returned. + /*!< also, the _outfile member variable is non-zero. */ + + void close_file(); + //!< shuts down the file, if any, we had opened for logging. + + // unavailable. + file_logger(const file_logger &); + file_logger &operator =(const file_logger &); +}; + +////////////// + +//! a macro that retasks the program-wide logger as a file_logger. +#define SETUP_FILE_LOGGER { \ + loggers::standard_log_base *old_log = loggers::program_wide_logger::set \ + (new loggers::file_logger(loggers::file_logger::log_file_for_app_name())); \ + WHACK(old_log); \ +} + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/filter_set.h b/nucleus/library/loggers/filter_set.h new file mode 100644 index 00000000..19b0baea --- /dev/null +++ b/nucleus/library/loggers/filter_set.h @@ -0,0 +1,76 @@ +#ifndef FILTER_SET_CLASS +#define FILTER_SET_CLASS + +/*****************************************************************************\ +* * +* Name : filter_set +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +namespace loggers { + +//! A simple object that wraps a templated set of ints. +class filter_set : public structures::set, public virtual basis::root_object +{ +public: + filter_set() {} + //!< Constructs an empty set of filters. + + virtual ~filter_set() {} + + filter_set(const structures::set &to_copy) : structures::set(to_copy) {} + //!< Constructs a copy of the "to_copy" array. + + DEFINE_CLASS_NAME("filter_set"); + + //! Adds a member to the filter set. + /*! The filter set is used to check all extended filter values passed to + log and print. if the special filters of ALWAYS_PRINT or NEVER_PRINT are + added, then either everything will be logged or nothing will be. */ + virtual void add_filter(int new_filter) { + basis::auto_synchronizer l(c_lock); + add(new_filter); + } + + //! Removes a member from the filter set. + virtual void remove_filter(int old_filter) { + basis::auto_synchronizer l(c_lock); + remove(old_filter); + } + + //! Returns true if the "filter_to_check" is a member of the filter set. + /*! If "filter_to_check" is ALWAYS_PRINT, this always returns true. If + the value is NEVER_PRINT, false is always returned. */ + virtual bool member(int filter_to_check) { + if (filter_to_check == basis::ALWAYS_PRINT) return true; + if (filter_to_check == basis::NEVER_PRINT) return false; + return structures::set::member(filter_to_check); + } + + //! Resets the filter set to be empty. + virtual void clear_filters() { + basis::auto_synchronizer l(c_lock); + clear(); + } + +private: + basis::mutex c_lock; +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/loggers/logging_filters.h b/nucleus/library/loggers/logging_filters.h new file mode 100644 index 00000000..43ae2b49 --- /dev/null +++ b/nucleus/library/loggers/logging_filters.h @@ -0,0 +1,41 @@ +#ifndef LOGGING_FILTERS_GROUP +#define LOGGING_FILTERS_GROUP + +////////////// +// Name : logging_filters +// Author : Chris Koeritz +////////////// +// Copyright (c) 2010-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include + +namespace loggers { + + enum logging_filters { + // synonyms for filters that are important enough to always show. + FILT_FATAL = basis::ALWAYS_PRINT, + FILT_ERROR = basis::ALWAYS_PRINT, + // the first really selectable filters follow... one might notice a + // small similarity to levels available with log4j. + DEFINE_FILTER(FILT_WARNING, 1, "Important or unusual condition occurred in the runtime."), + DEFINE_FILTER(FILT_INFO, 2, "Information from normal runtime activities."), + DEFINE_FILTER(FILT_DEBUG, 3, "Noisy debugging information from objects."), + // occasionally useful filters for locally defined categories. + DEFINE_FILTER(FILT_UNUSUAL, 4, "Unusual but not necessarily erroneous events."), + DEFINE_FILTER(FILT_NETWORK, 5, "Network activity and events."), + DEFINE_FILTER(FILT_DATABASE, 6, "Data storage activities or warnings.") + }; + +} //namespace. + +#endif + diff --git a/nucleus/library/loggers/logging_macros.h b/nucleus/library/loggers/logging_macros.h new file mode 100644 index 00000000..31547a41 --- /dev/null +++ b/nucleus/library/loggers/logging_macros.h @@ -0,0 +1,102 @@ +#ifndef LOGGING_MACROS_GROUP +#define LOGGING_MACROS_GROUP + +/*****************************************************************************\ +* * +* Name : logging macros * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +/*! @file logging_macros.h + these macros can assist in logging. they rely on the base_logger class and the program-wide + logger class for logging services. note that it is often convenient to customize one or more + of these to yield a simpler macro name per project, such as PRINT or LOG. +*/ + +#include +#include +#include + +///hmmm: very temporary until the call stack tracking is available again. +#define update_current_stack_frame_line_number(x) + +//! Logs a string "to_log" on "the_logger" using the "filter". +/*! The filter is checked before the string is allowed to come into +existence, which saves allocations when the item would never be printed +out anyway. */ +#define FILTER_LOG(the_logger, to_log, filter) { \ + if (the_logger.member(filter)) { \ + the_logger.log(to_log, filter); \ + } \ +} + +//! Logs a string at the emergency level, meaning that it always gets logged. +#define EMERGENCY_LOG(the_logger, to_log) \ + FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) + +//! Corresponding functions for including the time and date in the log entry. +#define STAMPED_FILTER_LOG(the_logger, to_log, filter) { \ + if (the_logger.member(filter)) { \ + astring temp_log = to_log; \ + if (temp_log.length()) \ + temp_log.insert(0, timely::time_stamp::notarize(true)); \ + the_logger.log(temp_log, filter); \ + } \ +} +//! Time-stamped logging that will always be printed. +#define STAMPED_EMERGENCY_LOG(the_logger, to_log) \ + STAMPED_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) + +//! Class specific logging method that uses a filter. +/* These add a class and function name to the log entry. */ +#define CLASS_FILTER_LOG(the_logger, to_log, filter) { \ + update_current_stack_frame_line_number(__LINE__); \ + if (the_logger.member(filter)) { \ + astring temp_log = to_log; \ + if (temp_log.length()) { \ + temp_log.insert(0, timely::time_stamp::notarize(true)); \ + BASE_FUNCTION(func); \ + temp_log += " ["; \ + temp_log += function_name; \ + temp_log += "]"; \ + } \ + the_logger.log(temp_log, filter); \ + } \ + update_current_stack_frame_line_number(__LINE__); \ +} +//! Class specific logging method that always prints. +#define CLASS_EMERGENCY_LOG(the_logger, to_log) \ + CLASS_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) + +//! Logs information that includes specific class instance information. +/*! This use the instance name of the object, which can include more +information than the simple class name. */ +#define INSTANCE_FILTER_LOG(the_logger, to_log, filter) { \ + update_current_stack_frame_line_number(__LINE__); \ + if (the_logger.member(filter)) { \ + astring temp_log = to_log; \ + if (temp_log.length()) { \ + temp_log.insert(0, timely::time_stamp::notarize(true)); \ + BASE_INSTANCE_FUNCTION(func); \ + temp_log += " ["; \ + temp_log += function_name; \ + temp_log += "]"; \ + } \ + the_logger.log(temp_log, filter); \ + update_current_stack_frame_line_number(__LINE__); \ + } \ +} +//! Logs with class instance info, but this always prints. +#define INSTANCE_EMERGENCY_LOG(the_logger, to_log) \ + INSTANCE_FILTER_LOG(the_logger, to_log, basis::ALWAYS_PRINT) + +#endif + diff --git a/nucleus/library/loggers/makefile b/nucleus/library/loggers/makefile new file mode 100644 index 00000000..dceb386c --- /dev/null +++ b/nucleus/library/loggers/makefile @@ -0,0 +1,10 @@ +include cpp/variables.def + +PROJECT = loggers +TYPE = library +SOURCE = combo_logger.cpp console_logger.cpp critical_events.cpp file_logger.cpp \ + program_wide_logger.cpp +TARGETS = loggers.lib + +include cpp/rules.def + diff --git a/nucleus/library/loggers/program_wide_logger.cpp b/nucleus/library/loggers/program_wide_logger.cpp new file mode 100644 index 00000000..c91d9771 --- /dev/null +++ b/nucleus/library/loggers/program_wide_logger.cpp @@ -0,0 +1,35 @@ +/*****************************************************************************\ +* * +* Name : program_wide_logger * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "program_wide_logger.h" + +using namespace basis; +using namespace loggers; + +namespace loggers { + +standard_log_base *program_wide_logger::c_the_wide_log = new null_logger; + +standard_log_base &program_wide_logger::get() { return *c_the_wide_log; } + +standard_log_base *program_wide_logger::set(standard_log_base *new_log) +{ + if (!new_log) return NIL; // can't fool me that easily. + standard_log_base *old_log = c_the_wide_log; + c_the_wide_log = new_log; + return old_log; +} + +} //namespace. + diff --git a/nucleus/library/loggers/program_wide_logger.h b/nucleus/library/loggers/program_wide_logger.h new file mode 100644 index 00000000..70280742 --- /dev/null +++ b/nucleus/library/loggers/program_wide_logger.h @@ -0,0 +1,84 @@ +#ifndef PROGRAM_WIDE_LOGGER_FACILITY +#define PROGRAM_WIDE_LOGGER_FACILITY + +/*****************************************************************************\ +* * +* Name : program_wide_logger facility * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "logging_macros.h" + // it seems reasonable to provide the logging macros to any file that is also using + // the program-wide logger. + +#include +#include +#include + +namespace loggers { + +//! A class that provides logging facilities vertically to all of hoople. +/*! + Provides a per-program logging subsystem that can be used from almost + anywhere in the source code. + The program wide logger feature is a globally defined object that + can be switched out to perform different types of logging. + Note: this facility is not thread-safe. The coder must guarantee + that the appropriate logger is set up before cranking up a bunch of + threads that use logging. +*/ + +class program_wide_logger +{ +public: + static loggers::standard_log_base &get(); + //!< Provided by the startup code within each application for logging. + /*!< This can be used like any base_logger object, but the diagnostic items + will be stored into one log file (or other logging mechanism) per + program. */ + + static loggers::standard_log_base *set(loggers::standard_log_base *new_log); + //!< replaces the current program-wide logger with the "new_log". + /*! The "new_log" must be a valid base_logger object. The responsibility + for maintaining "new_log" is taken over by the program-wide logger. + In return, the old program-wide logger is handed back to you and it is + now your responsibility. Be very careful with it if it's owned by other + code and not totally managed by you. */ + +private: + static loggers::standard_log_base *c_the_wide_log; //!< the actual logger. +}; + +////////////// + +//! a trash can for logging; throws away all entries. +/*! + Provides a logger that throws away all of the strings that are logged to it. + This is useful when an interface requires a base_logger but one does not + wish to generate an output file. This object is similar to /dev/null in Unix + (or Linux) and to nul: in Win32. +*/ + +class null_logger : public virtual standard_log_base +{ +public: + virtual ~null_logger() {} + DEFINE_CLASS_NAME("null_logger"); + virtual basis::outcome log(const basis::base_string &info, int filter) { + if (filter || !(&info)) {} + return basis::common::OKAY; + } +}; + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/loggers/standard_log_base.h b/nucleus/library/loggers/standard_log_base.h new file mode 100644 index 00000000..b27fdcd8 --- /dev/null +++ b/nucleus/library/loggers/standard_log_base.h @@ -0,0 +1,44 @@ +#ifndef STANDARD_LOG_BASE_CLASS +#define STANDARD_LOG_BASE_CLASS + +////////////// +// Name : standard_log_base +// Author : Chris Koeritz +////////////// +// Copyright (c) 2010-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include "eol_aware.h" +#include "filter_set.h" + +#include + +namespace loggers { + +//! A base class for a very usable logger with a filter_set and eol awareness. +/*! + We add this derived class of base_logger to incorporate some useful functionality + for managing filters without polluting the base class. This class allows the logging + functionality to not deal with a lot of add-ins or chicanery. +*/ + +class standard_log_base +: public virtual basis::base_logger, + public virtual basis::nameable, + public virtual filter_set, + public virtual eol_aware +{ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/makefile b/nucleus/library/makefile new file mode 100644 index 00000000..c96ec24a --- /dev/null +++ b/nucleus/library/makefile @@ -0,0 +1,31 @@ +include variables.def + +PROJECT = core_libraries +BUILD_BEFORE = algorithms \ + basis \ + structures \ + timely \ + mathematics \ + textual \ + nodes \ + filesystem \ + configuration \ + loggers \ + processes \ + application \ + unit_test \ + tests_basis \ + tests_algorithms \ + tests_structures \ + tests_filesystem \ + tests_mathematics \ + tests_nodes \ + tests_textual \ + tests_timely \ + tests_configuration \ + versions \ + crypto \ + tests_crypto + +include rules.def + diff --git a/nucleus/library/mathematics/averager.h b/nucleus/library/mathematics/averager.h new file mode 100644 index 00000000..e45dad9d --- /dev/null +++ b/nucleus/library/mathematics/averager.h @@ -0,0 +1,170 @@ +#ifndef AVERAGER_CLASS +#define AVERAGER_CLASS + +/*****************************************************************************\ +* * +* Name : averager * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1997-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace mathematics { + +//! Maintains a list of numbers and provides the average for them. +/*! + The list entries can be weighted if desired. If the list grows too large, + the first chunk of the entries is added as a weighted average and the list's + size is reduced appropriately. +*/ + +template +class averager +{ +public: + averager(int entries = 100, bool compacting = true); + //!< creates an averager whose list length is restricted to "entries". + /*!< if "entries" that is zero, then the maximum length is used. if + "compacting" is true, then the entries which need to be removed during + a compaction will be added to the list as a weighted average. if it's + false, then the unfortunate entries are just eliminated. the first style + leads to a more steady state version of the average while the other is + more recent history. note that the lowest reasonable value for "entries" + is eight due to the compaction algorithm; lower values will work, but the + averager will allow the list to grow to eight anyway. */ + + void add(contents value, int count = 1); + //!< adds a new "value" to the averager, with an optional "count". + + contents average() const { return average(0, length() - 1); } + //!< reports the overall average of the whole list. + + int samples() const; + //!< returns the total number of samples recorded in the average. + + int length() const { return _averages.length(); } + //!< returns the current length of the averages list. + + contents average(int start, int end) const; + //!< reports the average over the range from "start" to "end" inclusive. + + struct weighted_entry { contents value; int count; }; + //!< structure holding a weighted chunk of the average. + /*!< an entry in the list of averages has a "value" and a "count" that + measures the weight with which that "value" is considered. */ + + weighted_entry get(int index) const { return _averages.get(index); } + //!< accesses the entry stored at the "index" specified. + + void compact(); + //!< chops off the oldest portion of the averager. + /*!< if this is a compacting style averager, then the older data is + coalesced and added as a weighted entry. */ + + void check_for_compaction(); + //!< checks whether the averager needs to be compacted yet or not. + /*!< the decision is made according to the maximum allowable size in + "entries" passed to the constructor. if "entries" is zero, the maximum + allowable size is used instead. */ + +private: + bool _do_compaction; //!< do truncated values coalesce to a weighted entry? + basis::array _averages; //!< current list. + int _entries; //!< maximum length of list. +}; + +////////////// + +//! keeps an average on a stream of integers. +class int_averager : public averager +{ +public: + int_averager(int entries = 100, bool compacting = true) + : averager(entries, compacting) {} +}; + +////////////// + +// implementations below... + +const int AVERAGER_SIZE_LIMIT = 180000; // the most items we'll try to handle. + +template +averager::averager(int entries, bool compacting) +: _do_compaction(compacting), _averages(), _entries(entries) +{ + int unit_size = sizeof(weighted_entry); + if (basis::negative(_entries) || !_entries) + _entries = int(AVERAGER_SIZE_LIMIT / unit_size); +} + +template +void averager::compact() +{ + if (length() < 8) return; // sorry, you're too short for this wild ride. + int end_whacking = _averages.length() / 4; + if (_do_compaction) { + contents whacked_average = average(0, end_whacking); + _averages.zap(1, end_whacking); + _averages[0].value = whacked_average; + _averages[0].count = end_whacking + 1; + } else _averages.zap(0, end_whacking); +} + +template +void averager::check_for_compaction() +{ + // make sure that we don't go over our allotted space. + int unit_size = sizeof(weighted_entry); + int limit = basis::minimum(AVERAGER_SIZE_LIMIT, _entries * unit_size); + if (int(_averages.length() + 2) * unit_size >= limit) compact(); +} + +template +void averager::add(contents value, int count) +{ + weighted_entry to_add; + to_add.value = value; + to_add.count = count; + check_for_compaction(); + _averages += to_add; +} + +template +contents averager::average(int start, int end) const +{ + bounds_return(start, 0, length() - 1, 0); + bounds_return(end, start, length() - 1, 0); + bounds_return(end - start + 1, 1, length(), 0); + + contents accum = 0; + contents count = 0; + for (int i = start; i <= end; i++) { + accum += get(i).value * get(i).count; + count += get(i).count; + } + if (!count) count = 1; + return accum / count; +} + +template +int averager::samples() const +{ + int to_return = 0; + for (int i = 0; i < length(); i++) to_return += get(i).count; + return to_return; +} + +} //namespace. + +#endif + diff --git a/nucleus/library/mathematics/chaos.h b/nucleus/library/mathematics/chaos.h new file mode 100644 index 00000000..ffb1a310 --- /dev/null +++ b/nucleus/library/mathematics/chaos.h @@ -0,0 +1,113 @@ +#ifndef CHAOS_CLASS +#define CHAOS_CLASS + +////////////// +// Name : chaos +// Author : Chris Koeritz +////////////// +// Copyright (c) 1991-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +#include +#include +#include +#include + +//#define DEBUG_CHAOS + // uncomment for noisy logging. + +#ifdef DEBUG_CHAOS + #include +#endif +#include +#include +#ifdef __UNIX__ + #include +#endif + +namespace mathematics { + +// gets a 32 bit integer from the 15 bit random generator. it's 15 bit because +// rand() only generates numbers up to the MAX_RAND, which in the visual c++ +// case is limited to 0x7FFF. so, we let the first random number generated +// serve as the upper two bytes and we shift another one over a bit to cover +// the whole range of the third and fourth byte, except for that last bit, +// which is added in as a binary random value. +#define GET_32_BIT_RAND_YO \ + basis::un_int ranval = (basis::un_int(rand()) << 16) + (basis::un_int(rand()) << 1) \ + + (basis::un_int(rand()) % 2) + +//! a platform-independent way to acquire random numbers in a specific range. +/*! + This object also re-seeds the underlying system's random seed when retrain() + is invoked. +*/ + +class chaos : public virtual basis::nameable +{ +public: + chaos() { retrain(); } + virtual ~chaos() {} + + DEFINE_CLASS_NAME("chaos"); + + void retrain() + //!< Scrambles the OS's random seed and then provides pseudo-random values. + { + static unsigned int __flaxen = 0; + if (!__flaxen) { + time_t time_num; + time(&time_num); + // "t" is an ugly pointer into system data that contains the time nicely + // broken up into segments. the pointer cannot be freed! + tm *t = localtime(&time_num); + int add_in_milliseconds = basis::environment::system_uptime(); + // create a good random number seed from the time. +#ifdef DEBUG_CHAOS + printf("day %d hour %d min %d sec %d", (int)t->tm_mday, (int)t->tm_hour, + (int)t->tm_min, (int)t->tm_sec); +#endif + // we really don't care very much about the small chance of this being + // initialized to zero again. the chance of that happening more than once + // should be pretty astronomical. + __flaxen = (t->tm_sec + 60 * t->tm_mday + 60 * 31 * t->tm_hour + + 24 * 60 * 31 * t->tm_min) ^ add_in_milliseconds; + // initialize random generator. + srand(int(__flaxen)); + } + } + + // Keep in mind for the inclusive and exclusive functions that a range which + // is too large might not be dealt with well. This is because the random + // functions provided by the O.S. may not support the full range of integers. + + int inclusive(int low, int high) const + //!< Returns a pseudo-random number r, such that "low" <= r <= "high". + { + if (high < low) return low; + unsigned int range = high - low + 1; + GET_32_BIT_RAND_YO; + int adjusted = ranval % range + low; + return adjusted; + } + + int exclusive(int low, int high) const + //!< Returns a pseudo-random number r, such that "low" < r < "high". + { + if (high < low) return low + 1; + unsigned int range = high - low - 1; + GET_32_BIT_RAND_YO; + int adjusted = ranval % range + low + 1; + return adjusted; + } + +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/mathematics/double_plus.h b/nucleus/library/mathematics/double_plus.h new file mode 100644 index 00000000..25e68345 --- /dev/null +++ b/nucleus/library/mathematics/double_plus.h @@ -0,0 +1,101 @@ +#ifndef DOUBLE_PLUS_CLASS +#define DOUBLE_PLUS_CLASS + +/*****************************************************************************\ +* * +* Name : double_plus (an extension for double floating point numbers) * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "math_ops.h" + +#include +#include +#include + +#include //temp + +//! An extension to floating point primitives providing approximate equality. +/*! + Allows a programmer to ignore issues of rounding errors on floating point + numbers by specifying that two floating point numbers are equivalent if + they are equal within a small number "delta". This can help to eliminate + errors in floating point logic. +*/ + +namespace mathematics { + +class double_plus : public basis::orderable +{ +public: + #define DEFAULT_DELTA 0.0001 + /*!< the delta is the acceptable amount of difference between two floating + point numbers that are considered equivalent by this class. if they + differ by more than that, they are considered non-equivalent (and + hence must be greater than or less than each other). */ + + //! initializes using "init" as the initial value and equality within "delta". + double_plus(double init = 0.0, double delta = DEFAULT_DELTA) : c_value(init), c_delta(delta) {} + + //! initializes this from "to_copy". + double_plus(const double_plus &to_copy) : c_value(to_copy.c_value), c_delta(to_copy.c_delta) {} + + virtual ~double_plus() {} + + DEFINE_CLASS_NAME("double_plus"); + + //! standard assignment operator. + double_plus &operator = (const double_plus &cp) + { c_value = cp.c_value; c_delta = cp.c_delta; return *this; } + + double value() const { return truncate(); } + //!< observes the value held in this. + operator double () const { return truncate(); } + //!< observes the value held in this. + + double delta() const { return c_delta; } + //!< observes the precision for equality comparisons. + void delta(double new_delta) { c_delta = new_delta; } + //!< modifies the precision for equality comparisons. + + double truncate() const { return math_ops::round_it(c_value / c_delta) * c_delta; } + //!< returns a version of the number that is chopped off past the delta after rounding. + + //! returns true if this equals "f2" within the "delta" precision. + virtual bool equal_to(const basis::equalizable &f2) const { + const double_plus *cast = dynamic_cast(&f2); + if (!cast) return false; + return this->d_eq(*cast); + } + + //!< returns true if this is less than "f2". + virtual bool less_than(const basis::orderable &f2) const + { + const double_plus *cast = dynamic_cast(&f2); + if (!cast) return false; + return !this->d_eq(*cast) && (c_value < cast->c_value); + } + +private: + double c_value; //!< the contained floating point value. + double c_delta; //!< the offset within which equality is still granted. + + //! returns true if "to_compare" is within the delta of this. + bool d_eq(const double_plus &to_compare) const { + double diff = basis::absolute_value(c_value - to_compare.value()); + return diff < basis::absolute_value(c_delta); + } +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/mathematics/makefile b/nucleus/library/mathematics/makefile new file mode 100644 index 00000000..08038396 --- /dev/null +++ b/nucleus/library/mathematics/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = mathematics +TYPE = library +SOURCE = placeholder.cpp +TARGETS = mathematics.lib + +include cpp/rules.def + diff --git a/nucleus/library/mathematics/math_ops.h b/nucleus/library/mathematics/math_ops.h new file mode 100644 index 00000000..b2f7f879 --- /dev/null +++ b/nucleus/library/mathematics/math_ops.h @@ -0,0 +1,65 @@ +#ifndef MATH_OPS_GROUP +#define MATH_OPS_GROUP + +/*****************************************************************************\ +* * +* Name : mathematical operations * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace mathematics { + +//! A grab-bag of mathematical functions that are frequently useful. + +class math_ops +{ +public: + //! returns the rounded integer value for "to_round". + static int round_it(float to_round) + { + int to_return = int(to_round); + // this uses a simplistic view of rounding. + if (to_round - float(to_return) > 0.5) to_return++; + return to_return; + } + + //! returns the rounded integer value for "to_round". + static int round_it(double to_round) + { + int to_return = int(to_round); + // this uses a simplistic view of rounding. + if (to_round - double(to_return) > 0.5) to_return++; + return to_return; + } + +/* + //! returns the number two to the power "raise_to" (i.e. 2^raise_to). + static basis::u_int pow_2(const basis::u_int raise_to) + { + if (!raise_to) return 1; + basis::u_int to_return = 2; + for (basis::u_int i = 1; i < raise_to; i++) + to_return *= 2; + return to_return; + } +*/ + + //! returns n! (factorial), which is n * (n - 1) * (n - 2) ... + static basis::un_int factorial(int n) + { return (n < 2)? 1 : n * factorial(n - 1); } +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/mathematics/placeholder.cpp b/nucleus/library/mathematics/placeholder.cpp new file mode 100644 index 00000000..bc3d86fe --- /dev/null +++ b/nucleus/library/mathematics/placeholder.cpp @@ -0,0 +1,15 @@ + + +// sole purpose of this is to make the lib be generated, +// even though we currently do not have any code that lives inside it. + + +#include "double_plus.h" + +using namespace mathematics; + +int fu() +{ +double_plus ted; +return (int)ted; +} diff --git a/nucleus/library/nodes/list.cpp b/nucleus/library/nodes/list.cpp new file mode 100644 index 00000000..3429f8e7 --- /dev/null +++ b/nucleus/library/nodes/list.cpp @@ -0,0 +1,270 @@ + + + +/*****************************************************************************\ +* * +* Name : list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// POLICIES: +// +// the cursor should never be stored to or deleted; it is merely a scanner that +// runs through the list. +// +// the cursor can point at the head or tail. any storage action is taken to +// mean that it applies to the closest real object, if one exists. any query +// action is taken similarly. + +#include "list.h" +#include "node.h" + +#include + +namespace nodes { + +// nice names for the positions of the next link and the previous link in +// our node's indices. +const int PREVIOUS = 0; +const int NEXT = 1; + +////////////// + +// iterator functions: + +void list::iterator::operator ++() +{ + if (is_tail()) return; // already at the end. + _cursor = _cursor->get_link(NEXT); +} + +void list::iterator::operator --() +{ + if (is_head()) return; // already at the front. + _cursor = _cursor->get_link(PREVIOUS); +} + +bool list::iterator::operator ==(const iterator &to_compare) const +{ return _cursor == to_compare._cursor; } + +const node *list::iterator::observe() +{ + if (!_manager || _manager->empty()) return NIL; + if (*this == _manager->head()) next(); + if (*this == _manager->tail()) previous(); + return _cursor; +} + +node *list::iterator::access() +{ + if (!_manager || _manager->empty()) return NIL; + if (*this == _manager->head()) next(); + if (*this == _manager->tail()) previous(); + return _cursor; +} + +bool list::iterator::is_head() const +{ + if (!_manager) return false; + return _cursor == _manager->_head; +} + +bool list::iterator::is_tail() const +{ + if (!_manager) return false; + return _cursor == _manager->_tail; +} + +void list::iterator::jump_head() +{ + if (!_manager) return; + _cursor = _manager->_head; +} + +void list::iterator::jump_tail() +{ + if (!_manager) return; + _cursor = _manager->_tail; +} + +////////////// + +list::list() +: _head(NIL), _tail(NIL) +{ + _head = new node(2); + _tail = new node(2); + _head->set_link(NEXT, _tail); + _tail->set_link(PREVIOUS, _head); +} + +list::~list() +{ + iterator zapper = head(); + while (!empty()) + zap(zapper); + WHACK(_head); + WHACK(_tail); +} + +bool list::empty() const +{ + if (_head->get_link(NEXT) == _tail) return true; + return false; +} + +bool list::set_index(iterator &where, int new_index) +{ + if (where._manager != this) return false; + if (empty()) return false; + node *skipper = _head->get_link(NEXT); + for (int i = 0; i < new_index; i++) { + skipper = skipper->get_link(NEXT); + if (skipper == _tail) return false; // out of bounds now. + } + where._cursor = skipper; + return true; +} + +bool list::forward(iterator &where, int count) +{ + if (where._manager != this) return false; + if (count <= 0) return true; + if (items_from_tail(where) < count) return false; + if (where.is_head()) where.next(); // skip the head guard. + for (int i = 0; i < count; i++) where.next(); + return true; +} + +bool list::backward(iterator &where, int count) +{ + if (where._manager != this) return false; + if (count <= 0) return true; + if (items_from_head(where) < count) return false; + if (where.is_tail()) where.previous(); // skip the tail guard. + for (int i = 0; i < count; i++) where.previous(); + return true; +} + +int list::elements() const +{ + if (empty()) return 0; + int to_return = 0; + node *skipper = _head->get_link(NEXT); + while (skipper != _tail) { + to_return++; + skipper = skipper->get_link(NEXT); + } + return to_return; +} + +int list::items_from_head(const iterator &where) const +{ + if (where._manager != this) return 0; + if (where.is_head()) return 0; // make sure it's not there already. + int index = 0; + node *skipper = _head->get_link(NEXT); + while ( (where._cursor != skipper) && (skipper != _tail) ) { + index++; + skipper = skipper->get_link(NEXT); + } + return index; +} + +int list::items_from_tail(const iterator &where) const +{ + if (where._manager != this) return 0; + if (where.is_tail()) return 0; // make sure it's not there already. + int index = 0; + node *skipper = _tail->get_link(PREVIOUS); + while ( (where._cursor != skipper) && (skipper != _head) ) { + index++; + skipper = skipper->get_link(PREVIOUS); + } + return index; +} + +/* +node *list::get() +{ + if (empty()) return NIL; // make sure the list isn't empty. + if (_cursor == _head) return _head->get_link(NEXT); + // check special case for pointing at the head. + if (_cursor == _tail) return _tail->get_link(PREVIOUS); + // check special case for pointing at the tail. + return _cursor; +} +*/ + +node *list::remove(iterator &where) +{ + if (where._manager != this) return NIL; + if (empty()) return NIL; + if (where._cursor == _head) + where._cursor = _head->get_link(NEXT); + if (where._cursor == _tail) + where._cursor = _tail->get_link(PREVIOUS); + node *old_cursor = where._cursor; + node *old_previous = old_cursor->get_link(PREVIOUS); + node *old_next = old_cursor->get_link(NEXT); + old_cursor->set_link(PREVIOUS, NIL); + old_cursor->set_link(NEXT, NIL); + old_previous->set_link(NEXT, old_next); + old_next->set_link(PREVIOUS, old_previous); + where._cursor = old_next; + return old_cursor; +} + +void list::zap(iterator &where) { delete remove(where); } + +void list::append(iterator &where, node *new_node) +{ + if (where._manager != this) return; + while (new_node->links() < 2) new_node->insert_link(0, NIL); + if (empty()) where._cursor = _head; + if (where._cursor == _tail) + where._cursor = _tail->get_link(PREVIOUS); + // shift from the tail sentinel to the tail element. + node *save_next = where._cursor->get_link(NEXT); + where._cursor->set_link(NEXT, new_node); + new_node->set_link(PREVIOUS, where._cursor); + new_node->set_link(NEXT, save_next); + save_next->set_link(PREVIOUS, new_node); + where._cursor = new_node; +} + +void list::insert(iterator &where, node *new_node) +{ + if (where._manager != this) return; + while (new_node->links() < 2) new_node->insert_link(0, NIL); + if (empty()) where._cursor = _tail; + if (where._cursor == _head) + where._cursor = _head->get_link(NEXT); + // shift from head sentinel to the head element. + node *save_prior = where._cursor->get_link(PREVIOUS); + where._cursor->set_link(PREVIOUS, new_node); + new_node->set_link(NEXT, where._cursor); + new_node->set_link(PREVIOUS, save_prior); + save_prior->set_link(NEXT, new_node); + where._cursor = new_node; +} + +void list::zap_all() +{ + iterator zapper = head(); + while (!empty()) zap(zapper); +} + +} // namespace. + + + + diff --git a/nucleus/library/nodes/list.h b/nucleus/library/nodes/list.h new file mode 100644 index 00000000..455ebaa4 --- /dev/null +++ b/nucleus/library/nodes/list.h @@ -0,0 +1,190 @@ +#ifndef LIST_CLASS +#define LIST_CLASS + +/*****************************************************************************\ +* * +* Name : list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + + + +namespace nodes { + +class node; // forward. + +//! Implements a guarded, doubly linked list structure. +/*! + The list is viewed one element at a time, through the monocular of an + iterator, which keeps track of the current position in the list. the + iterator's cursor can be shifted around at will. nodes can be added to + the list before or after the cursor, and the node pointed at by the cursor + can be queried or modified. Using multiple iterators is fine as long as + you guarantee that they either are all just reading the list or that you + have a valid access pattern of reads and writes such that no iterator's + cursor is affected. Note that this list is not thread safe. +*/ + +class list +{ +public: + list(); + //!< constructs a blank list. + + ~list(); + //!< invalidates all contents of the list and destroys all child nodes. + + int elements() const; + //!< returns the number of items currently in the list. + /*!< this is a costly operation. */ + + bool empty() const; + //!< returns true if the list is empty. + /*!< this is really quick compared to checking the number of elements. */ + + //! iterators allow the list to be traversed. + /*! NOTE: it is an error to use an iterator on one list with a different + list; the methods will do nothing or return empty results in this + situation. */ + + class iterator { + public: + iterator(const list *mgr, node *start) : _cursor(start), _manager(mgr) {} + //!< opens up an iterator on a list. + /*!< the preferred method to construct an iterator is to use the + head/tail functions in list. */ + + void operator ++(); //!< go to next item. + void operator --(); //!< go to previous item. + void operator ++(int) { ++(*this); } //!< post-fix synonym for ++. + void operator --(int) { --(*this); } //!< post-fix synonym for --. + + + void next() { (*this)++; } //!< synonym for ++. + void previous() { (*this)--; } //!< synonyms for --. + + bool operator ==(const iterator &to_compare) const; + //!< returns true if the two iterators are at the same position. + + bool is_head() const; + //!< returns true if the cursor is at the head of the list. + /*!< Note: head() and tail() only return true if the iterator is + actually positioned _at_ the head or tail guard. for example, if the + cursor is pointing at the last actual element in the list (but not at + the guard itself), then is_tail() returns false. so, in iteration, + your iterator will actually go past the last valid element before + is_tail() returns true. thus it is very important to check is_tail() + *before* looking at the node with observe() or access(), since those + two will shift the list position to the nearest valid node and away + from the guard. */ + bool is_tail() const; + //!< returns true if the cursor is at the tail of the list. + + void jump_head(); //!< set the iterator to the head. + void jump_tail(); //!< set the iterator to the tail. + + const node *observe(); //!< peek at the current element. + /*!< Note: observe() and access() will return the first element if the + list is positioned at the head (or the last if at the tail), and will + not return NIL for these two positions as long as the list has some + elements in it. the cursor will also have been moved to point at the + element that is returned. + Another Note: it is extremely important that you do not mess with the + links owned by the node (or at least the first two links). + A Third Note: these two functions can return NIL if the list is empty. */ + node *access(); //!< obtain access to the current element. + + private: + node *_cursor; //!< the current position. + friend class list; //!< lists have full access to this object. + const list *_manager; //!< our friendly manager. + }; + + iterator head() const { return iterator(this, _head); } + //!< returns an iterator located at the head of the list. + iterator tail() const { return iterator(this, _tail); } + //!< returns an iterator located at the tail of the list. + + int index(const iterator &it) const { return items_from_head(it); } + //!< returns the zero-based index of the cursor's position from the head. + /*!< this is a synonym for items_from_head(). */ + + bool set_index(iterator &to_set, int new_index); + //!< positions the iterator "to_set" at the index specified. + + // storage and retrieval actions. + // Note: all of these methods shift the iterator onto the next valid node + // if it is positioned at the beginning or end of the list. + + void append(iterator &where, node *new_node); + //!< adds a "new_node" into the list immediately after "where". + /*!< the nodes previously following the current node (if any) will follow + after the "new_node". this does not change the current position. + ownership of "new_node" is taken over by the list. */ + + void insert(iterator &where, node *new_node); + //!< places a "new_node" immediately before the current node in the list. + /*!< the "new_node" will follow the prior precursor to the current node. + this does not change the current position. ownership of "new_node" + is taken over by the list. after the call, the iterator points at + the new node. */ + + node *remove(iterator &where); + //!< extracts the current item from "where" and repairs the hole. + /*!< NIL is returned if the list was empty. the current position is set + to the node that used to be after the node that has been removed. after + the call, the iterator points at the new node. */ + + void zap(iterator &where); + //!< wipes out the item at "where", including its contents. + /*!< the current position is reset to the item after the now defunct + item (or the tail). */ + + void zap_all(); + //!< destroys all the list elements and makes the list empty. + /*!< any existing iterators will be invalidated by this. */ + + // the following positioning functions could fail if the request is out of + // bounds. for example, forward cannot go beyond the end of the list. in + // such cases, false is returned and the list pointer is not moved. + + bool forward(iterator &where, int count); + //!< moves the list pointer "count" items towards the tail. + /*!< Note: forward and backward operate on elements; the head and tail + guard are not included in the count. Also, negative values of "count" + are ignored. */ + bool backward(iterator &where, int count); + //!< moves the list pointer "count" items towards the head. + + int items_from_head(const iterator &where) const; + //!< Returns the number of elements between the current item and the head. + /*!< zero is returned if this is at the first element or the head. */ + int items_from_tail(const iterator &where) const; + //!< Returns the number of elements between the current item and the tail. + /*!< zero is returned if this is at the last element or the tail. */ + +private: + friend class iterator; + node *_head; //!< pointer to the first item. + node *_tail; //!< pointer to the last item. + + bool skip_or_ignore(iterator &where, int count); + //!< zips the list to the position indicated by "count", if it can. + + // not permitted. + list(const list &); + list &operator =(const list &); +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/nodes/makefile b/nucleus/library/nodes/makefile new file mode 100644 index 00000000..b114f9e7 --- /dev/null +++ b/nucleus/library/nodes/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = nodes +TYPE = library +SOURCE = list.cpp node.cpp packable_tree.cpp path.cpp symbol_tree.cpp tree.cpp +TARGETS = nodes.lib + +include cpp/rules.def + diff --git a/nucleus/library/nodes/node.cpp b/nucleus/library/nodes/node.cpp new file mode 100644 index 00000000..465fa5df --- /dev/null +++ b/nucleus/library/nodes/node.cpp @@ -0,0 +1,103 @@ +/*****************************************************************************\ +* * +* Name : node * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "node.h" + +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace nodes { + +// the internal_link class anonymously hangs onto a pointer to the object. +struct internal_link { + node *_connection; + internal_link(node *destination = NIL) : _connection(destination) {} + virtual ~internal_link() { _connection = NIL; } +}; + +class node_link_amorph : public amorph +{ +public: + node_link_amorph(int num) : amorph(num) {} +}; + +////////////// + +node::node(int number_of_links) +: _links(new node_link_amorph(number_of_links)) +{ for (int i = 0; i < number_of_links; i++) set_empty(i); } + +node::~node() +{ + _links->reset(); + WHACK(_links); +} + +int node::links() const { return _links->elements(); } + +// set_empty: assumes used correctly by internal functions--no bounds return. +void node::set_empty(int link_num) +{ + internal_link *blank_frank = new internal_link(NIL); + _links->put(link_num, blank_frank); +} + +#define test_arg(link_num) bounds_return(link_num, 0, _links->elements()-1, ); + +void node::set_link(int link_number, node *new_link) +{ + test_arg(link_number); + (*_links)[link_number]->_connection = new_link; +} + +void node::zap_link(int link_number) +{ + test_arg(link_number); + _links->zap(link_number, link_number); +} + +void node::insert_link(int where, node *to_insert) +{ + // make sure that the index to insert at will not be rejected by the + // amorph insert operation. + if (where > links()) + where = links(); + _links->insert(where, 1); + set_empty(where); + set_link(where, to_insert); +} + +node *node::get_link(int link_number) const +{ + bounds_return(link_number, 0, _links->elements()-1, NIL); + return (*_links)[link_number]->_connection; +} + +int node::which(node *branch_to_find) const +{ + int to_return = common::NOT_FOUND; + for (int i = 0; i <= links() - 1; i++) + if (branch_to_find == get_link(i)) { + to_return = i; + break; + } + return to_return; +} + +} // namespace. + diff --git a/nucleus/library/nodes/node.h b/nucleus/library/nodes/node.h new file mode 100644 index 00000000..1b1a6d64 --- /dev/null +++ b/nucleus/library/nodes/node.h @@ -0,0 +1,137 @@ +#ifndef NODE_CLASS +#define NODE_CLASS + +/*****************************************************************************\ +* * +* Name : node * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace nodes { + +// forward: +class node_link_amorph; + +//! An object representing the interstitial cell in most linked data structures. +/*! + The node is intended as an extensible base class that provides general + connectivity support. Nodes can be connected to each other in + arbitrary ways, but often a derived data type provides more structured + organization. When a node's link is zapped, only the connection is + cut; no destruction is performed. + + Examples: A linked list can be represented as a node with one link that + connects to the succeeding item in the list. A doubly linked list can + be represented by a node with two links; one to the previous node and + the other to the next node. The most general structure might be an + arbitrary graph that can connect nodes to any number of other nodes. +*/ + +class node : public virtual basis::root_object +{ +public: + node(int number_of_links = 0); + //!< the constructor provides for "number_of_links" links initially. + /*!< the table below gives some common numbers for links for a variety of + data structures: @code + + Links Data Structure Purpose of Link(s) + ------ ------------------------- ---------------------------------- + 1 singly linked list points to next node in list + 2 doubly linked list one to previous node, one to next + 2 binary tree point to the two children + n n-ary tree point to the n children + n+1 n-ary doubly linked tree point to n children and 1 parent + m m-ary graph node point to m relatives. + @endcode */ + + virtual ~node(); + //!< the destructor simply invalidates the node. + /*!< this does not rearrange the links as would be appropriate for a + data structure in which only the node to be destroyed is being eliminated. + policies regarding the correct management of the links must be made in + objects derived from node. */ + + int links() const; + //!< Returns the number of links the node currently holds. + + void set_link(int link_number, node *new_link); + //!< Connects the node "new_link" to this node. + /*!< the previous link is lost, but not modified in any way. the address + of the new link is used directly--no copy of the node is made. setting a + link to a null node pointer clears that link. */ + + node *get_link(int link_number) const; + //!< Returns the node that is connected to the specified "link_number". + /*!< if the link is not set, then NIL is returned. */ + + void zap_link(int link_number); + //!< the specified link is removed from the node. + + void insert_link(int where, node *to_add = NIL); + //!< adds a new link prior to the position specified in "where". + /*!< thus a "where" value of less than or equal to zero will add a new + link as the first element. a "where" value greater than or equal to + links() will add a new link after the last element. the node "to_add" + will be stored in the new link. */ + + int which(node *to_find) const; + //!< locates the index where "to_find" lives in our list of links. + /*!< returns the link number for a particular node that is supposedly + connected to this node or common::NOT_FOUND if the node is not one + of the children. */ + +private: + node_link_amorph *_links; //!< the list of connections to other nodes. + + void set_empty(int link_number); + //!< clears the link number specified. + + // disallowed: + node(const node &); + node &operator =(const node &); +}; + +////////////// + +//! the basket class holds an object and supports connecting them as nodes. +/*! + the templated object is required to provide both a default constructor + and a copy constructor. +*/ + +template +class basket : public node +{ +public: + basket(int links, const contents &to_store = contents()) + : node(links), _storage(to_store) {} + + basket(const basket &to_copy) { *this = to_copy; } + + basket &operator = (const contents &to_copy) + { if (&to_copy != this) _storage = to_copy; return *this; } + + const contents &stored() const { return _storage; } + //!< allows a peek at the stored object. + contents &stored() { return _storage; } + //!< provides access to the stored object. + +private: + contents _storage; +}; + +} // end namespace. + +#endif + diff --git a/nucleus/library/nodes/packable_tree.cpp b/nucleus/library/nodes/packable_tree.cpp new file mode 100644 index 00000000..a38f88ad --- /dev/null +++ b/nucleus/library/nodes/packable_tree.cpp @@ -0,0 +1,226 @@ +/*****************************************************************************\ +* * +* Name : packable_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "packable_tree.h" + +#include +#include +#include +#include +#include + +using namespace basis; +using namespace structures; + +//#define DEBUG_PACKABLE_TREE + // uncomment for noisy debugging. + +#undef LOG +#ifdef DEBUG_PACKABLE_TREE + #include + #define LOG(to_print) printf("%s\n", astring(to_print).s()); +#else + #define LOG(s) { if (!!s) {} } +#endif + +namespace nodes { + +// tree commands are used to tell the unpacker what to do with the blobs +// it finds. BRANCHES_FOLLOW indicates that there are a few branches stored +// at the next few contiguous memory positions. ATTACH_BRANCHES means that +// the next branch should be the parent of some number of previous branches. +// FINISH means that the tree is done being stored (or reconstructed). +enum tree_commands { BRANCHES_FOLLOW, ATTACH_BRANCHES, FINISH }; + +////////////// + +packable_tree_factory::~packable_tree_factory() {} + +////////////// + +//! the TCU stores a command about this packed unit's purpose, the number of branches held, and the size of the contents at this node. +struct tree_command_unit : public virtual packable +{ + tree_commands command; + int number; + int size; + + virtual ~tree_command_unit() {} + + virtual int packed_size() const { return 3 * PACKED_SIZE_INT32; } + + virtual void pack(byte_array &packed_form) const { + attach(packed_form, int(command)); + attach(packed_form, number); + attach(packed_form, size); + } + + virtual bool unpack(byte_array &packed_form) { + int cmd; + if (!detach(packed_form, cmd)) return false; + command = (tree_commands)cmd; + if (!detach(packed_form, number)) return false; + if (!detach(packed_form, size)) return false; + return true; + } +}; + +////////////// + +packable_tree::packable_tree() : tree() {} + +void packable_tree::calcit(int &size_accumulator, const packable_tree *current_node) +{ +LOG(a_sprintf("calcing node %x", current_node)); + FUNCDEF("calcit"); + if (!current_node) throw_error(static_class_name(), func, "current node is nil"); + tree_command_unit temp; + size_accumulator += current_node->packed_size() + temp.packed_size(); +LOG(a_sprintf("len A %d", size_accumulator)); +} + +void packable_tree::packit(byte_array &packed_form, const packable_tree *current_node) +{ +LOG(a_sprintf("packing node %x", current_node)); +LOG(a_sprintf("size A %d", packed_form.length())); + FUNCDEF("packit"); + if (!current_node) throw_error(static_class_name(), func, "current node is nil"); + + byte_array temp_store; + +int guess = current_node->packed_size(); + + current_node->pack(temp_store); + +if (temp_store.length() != guess) +throw_error(current_node->class_name(), func, "failure calculating size"); + + tree_command_unit command; + command.size = temp_store.length(); +//hmmm: do we still need a packed size? + if (current_node->branches() == 0) { + command.command = BRANCHES_FOLLOW; + // the branches following are always just one branch. + command.number = 1; + } else { + command.command = ATTACH_BRANCHES; + command.number = current_node->branches(); + } + // stuff the command unit. + command.pack(packed_form); +LOG(a_sprintf("size B %d", packed_form.length())); + packed_form += temp_store; // main chunk is not packed, just added. +LOG(a_sprintf("size C %d", packed_form.length())); +} + +int packable_tree::recursive_packed_size() const +{ + packable_tree *curr = NIL; + int accum = 0; // where we accumulate the length of the packed form. + for (iterator zip2 = start(postfix); (curr = (packable_tree *)zip2.next()); ) + calcit(accum, curr); + tree_command_unit end_command; + accum += end_command.packed_size(); + return accum; +} + +void packable_tree::recursive_pack(byte_array &packed_form) const +{ + packable_tree *curr = NIL; + for (iterator zip2 = start(postfix); (curr = (packable_tree *)zip2.next()); ) + packit(packed_form, curr); + + tree_command_unit end_command; + end_command.number = 1; + end_command.command = FINISH; + end_command.size = 0; + // end command is stored at end. + end_command.pack(packed_form); +} + +packable_tree *packable_tree::recursive_unpack(byte_array &packed_form, + packable_tree_factory &creator) +{ + stack accumulated_trees(0); // unbounded. + tree_command_unit cmd; + // get the first command out of the package. + if (!cmd.unpack(packed_form)) { +//complain. + return false; + } + + packable_tree *new_branch = NIL; + bool failure = false; // set to true if errors occurred. + + // the packed tree is traversed by grabbing a command and then doing what + // it says as far as pulling in children or adding a new branch. + while (cmd.command != FINISH) { + new_branch = creator.create(); + + new_branch->unpack(packed_form); + + if (cmd.command == ATTACH_BRANCHES) { + if (cmd.number > accumulated_trees.size()) { +//log instead: "badly formed packed tree" + failure = true; + break; + } + for (int i = cmd.number; i > 0; i--) { + packable_tree *to_add = (packable_tree *)accumulated_trees + [accumulated_trees.size()-i]; + new_branch->attach(to_add); + } + packable_tree *junk; + for (int j = 0; j < cmd.number; j++) + accumulated_trees.acquire_pop(junk); + accumulated_trees.push(new_branch); + } else if (cmd.command == BRANCHES_FOLLOW) { + accumulated_trees.push(new_branch); + } else { +//log instead: "invalid command in packed tree" + failure = true; + break; + } + if (!cmd.unpack(packed_form)) { +//complain. + failure = true; + break; + } + } + + if (accumulated_trees.size() != 1) { +//log instead: "not all branches were claimed" + failure = true; + } else if (!failure) { + packable_tree *junk; + accumulated_trees.acquire_pop(junk); + } + + // clean up the allocated objects if we saw a failure. + if (failure) { + while (true) { + packable_tree *to_whack; + outcome ret = accumulated_trees.acquire_pop(to_whack); + if (ret == common::IS_EMPTY) break; + if (to_whack != new_branch) + WHACK(to_whack); + } + WHACK(new_branch); + } + + return new_branch; +} + +} // namespace. + diff --git a/nucleus/library/nodes/packable_tree.h b/nucleus/library/nodes/packable_tree.h new file mode 100644 index 00000000..a003db04 --- /dev/null +++ b/nucleus/library/nodes/packable_tree.h @@ -0,0 +1,74 @@ +#ifndef PACKABLE_TREE_CLASS +#define PACKABLE_TREE_CLASS + +/*****************************************************************************\ +* * +* Name : packable_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "tree.h" + +#include + +namespace nodes { + +// forward. +class packable_tree_factory; + +//! A tree object that can be packed into an array of bytes and unpacked again. + +class packable_tree : public tree, public virtual basis::packable +{ +public: + packable_tree(); + //!< constructs a new tree with a root and zero branches. + + // the recursive packing methods here will operate on all nodes starting at this one + // and moving downwards to all branches. + + int recursive_packed_size() const; + //!< spiders the tree starting at this node to calculate the packed size. + + void recursive_pack(basis::byte_array &packed_form) const; + //!< packs the whole tree starting at this node into the packed form. + + static packable_tree *recursive_unpack(basis::byte_array &packed_form, + packable_tree_factory &creator); + //!< unpacks a tree stored in "packed_form" and returns it. + /*!< if NIL is returned, then the unpack failed. the "creator" is needed + for making new derived packable_tree objects of the type stored. */ + + // standard pack, unpack and packed_size methods must be implemented by the derived tree. + +private: + static void packit(basis::byte_array &packed_form, const packable_tree *current_node); + //!< used by our recursive packing methods. + static void calcit(int &size_accumulator, const packable_tree *current_node); + //!< used by recursive packed size calculator. +}; + +////////////// + +class packable_tree_factory +{ +public: + virtual ~packable_tree_factory(); + virtual packable_tree *create() = 0; + //!< a tree factory is needed when we are recreating the packed tree. + /*!< this is because the real type held is always a derived object. + this method should just create a blank object of the appropriate type. */ +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/nodes/path.cpp b/nucleus/library/nodes/path.cpp new file mode 100644 index 00000000..d84325a7 --- /dev/null +++ b/nucleus/library/nodes/path.cpp @@ -0,0 +1,97 @@ +/*****************************************************************************\ +* * +* Name : path * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "node.h" +#include "path.h" + +#include +#include + +#include + +using namespace basis; +using namespace structures; + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +namespace nodes { + +class path_node_stack : public stack +{ +public: + path_node_stack() : stack(0) {} +}; + +////////////// + +path::path(const node *start) +: _stack(new path_node_stack) +{ _stack->push(const_cast(start)); } + +path::path(const path &to_copy) +: _stack(new path_node_stack(*to_copy._stack)) +{} + +path::~path() +{ + while (_stack->elements()) _stack->pop(); + WHACK(_stack); +} + +node *path::operator [] (int index) const { return (*_stack)[index]; } + +int path::size() const { return _stack->size(); } + +node *path::root() const { return (*_stack)[0]; } + +node *path::current() const { return _stack->top(); } + +node *path::follow() const { return _stack->top(); } + +path &path::operator = (const path &to_copy) +{ + if (this == &to_copy) return *this; + *_stack = *to_copy._stack; + return *this; +} + +node *path::pop() +{ + node *to_return; + if (_stack->acquire_pop(to_return) != common::OKAY) + return NIL; + return to_return; +} + +outcome path::push(node *to_add) +{ return _stack->push(to_add); } + +outcome path::push(int index) +{ + if (!_stack->top()->get_link(index)) return common::NOT_FOUND; + return _stack->push(_stack->top()->get_link(index)); +} + +bool path::generate_path(node *to_locate, path &to_follow) const +{ + FUNCDEF("generate_path") + +if (to_locate || to_follow.current()) {} +LOG("hmmm: path::generate_path is not implemented."); +return false; +} + +} // namespace. + diff --git a/nucleus/library/nodes/path.h b/nucleus/library/nodes/path.h new file mode 100644 index 00000000..af6261f4 --- /dev/null +++ b/nucleus/library/nodes/path.h @@ -0,0 +1,93 @@ +#ifndef PATH_CLASS +#define PATH_CLASS + +/*****************************************************************************\ +* * +* Name : path * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace nodes { + +// forward: +class node; +class path_node_stack; + +//! A method for tracing a route from a tree's root to a particular node. +/*! + A path has a starting point at a particular node and a list of links to + take from that node to another node. For the path to remain valid, none + of the links contained within it may be destroyed. +*/ + +class path : public virtual basis::nameable +{ +public: + path(const node *root); + //!< the path is relative to the "root" node. + /*!< this will be the first object pushed onto the stack that we use + to model the path. */ + + path(const path &to_copy); + + ~path(); + + DEFINE_CLASS_NAME("path"); + + path &operator =(const path &to_copy); + + int size() const; + //!< returns the number of items in the path. + + node *root() const; + //!< returns the relative root node for this path. + + node *current() const; + node *follow() const; + //!< Returns the node specified by this path. + /*!< if the path is not valid, NIL is returned. */ + + node *pop(); + //!< returns the top node on the path stack. + /*!< this returns the node at the farthest distance from the relative + root node and removes it from this path. */ + + basis::outcome push(node *to_add); + //!< puts the node "to_add" on the top of the stack. + /*!< adds a node to the path as long as "to_add" is one of the current + node's descendants. */ + + basis::outcome push(int index); + //!< indexed push uses the current top node's index'th link as new top. + /*!< adds the node at "index" of the current top node to the path, + providing that such a link number exists on the current node. */ + + bool generate_path(node *to_locate, path &to_follow) const; + //!< finds the way to get from the root to the "to_locate" node. + /*!< returns true if there is a path between the relative root of + this path and the node "to_locate". if there is such a path, + "to_follow" is set to one of the possible paths. */ + + node *operator [] (int index) const; + //!< returns the node stored at "index", or NIL if "index" is invalid. + +private: + path_node_stack *_stack; //!< implementation of our pathway. +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/nodes/symbol_tree.cpp b/nucleus/library/nodes/symbol_tree.cpp new file mode 100644 index 00000000..a4e455a5 --- /dev/null +++ b/nucleus/library/nodes/symbol_tree.cpp @@ -0,0 +1,227 @@ +/*****************************************************************************\ +* * +* Name : symbol_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "symbol_tree.h" + +#include +#include +#include +#include + +//#define DEBUG_SYMBOL_TREE + // uncomment for totally noisy version. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +using namespace basis; +using namespace structures; +using namespace textual; + +namespace nodes { + +class symbol_tree_associations : public symbol_table +{ +public: + symbol_tree_associations(int estimated_elements) + : symbol_table(estimated_elements) {} +}; + +////////////// + +symbol_tree::symbol_tree(const astring &node_name, int estimated_elements) +: tree(), + _associations(new symbol_tree_associations(estimated_elements)), + _name(new astring(node_name)) +{ +} + +symbol_tree::~symbol_tree() +{ + WHACK(_name); + WHACK(_associations); +} + +int symbol_tree::children() const { return _associations->symbols(); } + +const astring &symbol_tree::name() const { return *_name; } + +int symbol_tree::estimated_elements() const { return _associations->estimated_elements(); } + +void symbol_tree::rehash(int estimated_elements) { _associations->rehash(estimated_elements); } + +void symbol_tree::hash_appropriately(int estimated_elements) +{ _associations->hash_appropriately(estimated_elements); } + +bool symbol_tree::add(symbol_tree *to_add) +{ +#ifdef DEBUG_SYMBOL_TREE + FUNCDEF("add"); + LOG(astring("adding node for ") + to_add->name()); +#endif + attach(to_add); // add to tree. + _associations->add(to_add->name(), to_add); // add to associations. + return true; +} + +outcome symbol_tree::prune(tree *to_zap_in) +{ +#ifdef DEBUG_SYMBOL_TREE + FUNCDEF("prune"); +#endif + symbol_tree *to_zap = dynamic_cast(to_zap_in); + if (!to_zap) + throw("error: symbol_tree::prune: wrong type of node in prune"); +#ifdef DEBUG_SYMBOL_TREE + LOG(astring("zapping node for ") + to_zap->name()); +#endif + int found = which(to_zap); // find the node in the tree. + if (negative(found)) return common::NOT_FOUND; // not found. +#ifdef DEBUG_SYMBOL_TREE + int kids = _associations->symbols(); +#endif + _associations->whack(to_zap->name()); // remove from associations. +#ifdef DEBUG_SYMBOL_TREE + if (kids - 1 != _associations->symbols()) + throw("error: symbol_tree::prune: failed to crop kid in symtab"); +#endif + tree::prune(to_zap); // remove from tree. + return common::OKAY; +} + +symbol_tree *symbol_tree::branch(int index) const +{ return dynamic_cast(tree::branch(index)); } + +// implementation snagged from basis/shell_sort. +void symbol_tree::sort() +{ + int n = branches(); + symbol_tree *temp; + int gap, i, j; + for (gap = n / 2; gap > 0; gap /= 2) { + for (i = gap; i < n; i++) { + for (j = i - gap; j >= 0 && branch(j)->name() > branch(j + gap)->name(); + j = j - gap) { + // swap the elements that are disordered. + temp = branch(j + gap); + prune_index(j + gap); + insert(j, temp); + temp = branch(j + 1); + prune_index(j + 1); + insert(j + gap, temp); + } + } + } +} + +symbol_tree *symbol_tree::find(const astring &to_find, find_methods how, + string_comparator_function *comp) +{ +#ifdef DEBUG_SYMBOL_TREE + FUNCDEF("find"); +#endif + if (comp == NIL) comp = astring_comparator; +#ifdef DEBUG_SYMBOL_TREE + LOG(astring("finding node called ") + to_find); +#endif + // ensure that we compare the current node. + if (comp(name(), to_find)) return this; + + // perform the upward recursion first, since it's pretty simple. + if (how == recurse_upward) { + symbol_tree *our_parent = dynamic_cast(parent()); + if (!our_parent) return NIL; // done recursing. + return our_parent->find(to_find, how, comp); + } + + // see if our branches match the search term. + symbol_tree **found = _associations->find(to_find, comp); +#ifdef DEBUG_SYMBOL_TREE + if (!found) LOG(to_find + " was not found.") + else LOG(to_find + " was found successfully."); +#endif + if (!found) { + if (how == recurse_downward) { + // see if we can't find that name in a sub-node. + symbol_tree *answer = NIL; + for (int i = 0; i < branches(); i++) { + // we will try each branch in turn and see if it has a child named + // appropriately. + symbol_tree *curr = dynamic_cast(branch(i)); +#ifdef DEBUG_SYMBOL_TREE + LOG(astring("recursing to ") + curr->name()); +#endif + if (curr) + answer = curr->find(to_find, how, comp); + if (answer) + return answer; + } + } + return NIL; + } + return *found; +} + +//hmmm: is this useful elsewhere? +astring hier_prefix(int depth, int kids) +{ + astring indent = string_manipulation::indentation( (depth - 1) * 2); + if (!depth) return ""; + else if (!kids) return indent + "|--"; + else return indent + "+--"; +} + +astring symbol_tree::text_form() const +{ +#ifdef DEBUG_SYMBOL_TREE + FUNCDEF("text_form"); +#endif + astring to_return; + + tree::iterator ted = start(prefix); + // create our iterator to do a prefix traversal. + + tree *curr = (tree *)ted.next(); + +//hmmm: this cast assumes that the tree only contains trees. for more +// safety, we might want a dynamic cast here also. + while (curr) { + // we have a good directory to show. + symbol_tree *curr_cast = dynamic_cast(curr); + if (!curr_cast) { + // something very bad with that... +#ifdef DEBUG_SYMBOL_TREE + LOG("logic error: unknown type in symbol tree."); +#endif + ted.next(); + continue; + } + astring name_to_log = curr_cast->name(); + if (!ted.size()) name_to_log = ""; +#ifdef DEBUG_SYMBOL_TREE + LOG(a_sprintf("depth %d kids %d name %s", ted.size(), curr_cast->branches(), + name_to_log.s())); +#endif + to_return += hier_prefix(curr->depth(), curr_cast->branches()); + to_return += name_to_log; + to_return += parser_bits::platform_eol_to_chars(); + + curr = (tree *)ted.next(); + } + + return to_return; +} + +} // namespace. + diff --git a/nucleus/library/nodes/symbol_tree.h b/nucleus/library/nodes/symbol_tree.h new file mode 100644 index 00000000..c8d4f6dc --- /dev/null +++ b/nucleus/library/nodes/symbol_tree.h @@ -0,0 +1,109 @@ +#ifndef SYMBOL_TREE_CLASS +#define SYMBOL_TREE_CLASS + +/*****************************************************************************\ +* * +* Name : symbol_tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "tree.h" + +#include +#include + +namespace nodes { + +// forward. +class symbol_tree_associations; + +//! A symbol table that supports scope nesting and/or trees of symbol tables. +/*! + Note: although the symbol_tree is a tree, proper functioning is only + guaranteed if you stick to its own add / find methods rather than calling + on the base class's methods... but the tree's iterator support should be + used for traversing the symbol_tree and prune should work as expected. +*/ + +class symbol_tree : public tree +{ +public: + symbol_tree(const basis::astring &node_name, int estimated_elements = 100); + //!< creates a symbol_tree node with the "node_name". + /*!< presumably this could be a child of another symbol tree also. the "estimated_elements" + is used to choose a size for the table holding the names. */ + + virtual ~symbol_tree(); + //!< all child nodes will be deleted too. + /*!< if the automatic child node deletion is not good for your purposes, + be sure to unhook the children before deletion of the tree and manage them + separately. */ + + DEFINE_CLASS_NAME("symbol_tree"); + + int children() const; //!< returns the number of children of this node. + + const basis::astring &name() const; //!< returns the name of this node. + + int estimated_elements() const; //!< returns the number of bits in this node's table. + + symbol_tree *branch(int index) const; //!< returns the "index"th branch. + + void rehash(int estimated_elements); + //!< resizes the underlying symbol_table for this node. + + void hash_appropriately(int estimated_elements); + //!< resizes the hashing parameter to limit bucket sizes. + /*!< rehashes the name table so that there will be no more (on average) + than "max_per_bucket" items per hashing bucket. this is the max that will + need to be crossed to find an item, so reducing the number per bucket + speeds access but also requires more memory. */ + + bool add(symbol_tree *to_add); + //!< adds a child to this symbol_tree. + + virtual basis::outcome prune(tree *to_zap); + //!< removes a sub-tree "to_zap". + /*!< the "to_zap" tree had better be a symbol_tree; we are just matching + the lower-level virtual function prototype. note that the tree node + "to_zap" is not destroyed; it is just plucked from the tree. */ + + enum find_methods { just_branches, recurse_downward, recurse_upward }; + + symbol_tree *find(const basis::astring &to_find, + find_methods how, +///= just_branches, + basis::string_comparator_function *comp = NIL); + //!< returns the node specified by "to_find" or NIL. + /*!< this should be fairly quick due to the symbol table's hashing. + the "how" method specifies the type of recursion to be used in searching + if any. if "how" is passed as "just_branches", then only the branches are + checked and no recursion upwards or downwards is performed. if "how" is + "recurse_downward", then all sub-trees under the branches are checked + also. if "how" is given as "recurse_upward", then "this" node and parent + nodes are checked. the "comp" parameter will be used for comparing the + strings if it's passed as non-NIL. */ + + void sort(); + //!< sorts the sub-nodes of this symbol_tree. + + basis::astring text_form() const; + //!< traverses the tree to build a textual list of the nodes. + +private: + symbol_tree_associations *_associations; //!< the link from names to nodes. + basis::astring *_name; //!< the name of this symbol tree node. +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/nodes/tree.cpp b/nucleus/library/nodes/tree.cpp new file mode 100644 index 00000000..a087b0b7 --- /dev/null +++ b/nucleus/library/nodes/tree.cpp @@ -0,0 +1,391 @@ +/*****************************************************************************\ +* * +* Name : tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "tree.h" + +#include +#include +#include + +//#define DEBUG_TREE + // uncomment if you want lots of debugging info. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +using namespace basis; + +namespace nodes { + +const int BACKWARDS_BRANCH = 0; + // BACKWARDS_BRANCH is the branch from this tree to its parent. this is + // steeped in the perspective that the root is the backwards direction (where + // we came from, in a sense) and that the children of this node are the + // forwards direction. + +////////////// + +// iterator methods: + +tree::iterator::iterator(const tree *initial, traversal_directions direction) +: path(initial), _order(direction), _aim(AWAY_FROM_ROOT) +{ +} + +tree::iterator::~iterator() { while (size()) pop(); } + +bool tree::iterator::next_node(tree *&to_return) +{ +#ifdef DEBUG_TREE + FUNCDEF("next_node"); +#endif + to_return = NIL; +#ifdef DEBUG_TREE + if ( (_order != to_branches) + && (_order != reverse_branches) ) { + if (_aim == AWAY_FROM_ROOT) LOG("going down") + else LOG("going up"); + } +#endif + switch (_order) { + case prefix: { + if (_aim == AWAY_FROM_ROOT) { + // going down means this is the first time we have seen the current top + // node on the stack. + to_return = (tree *)(*this)[size() - 1]; +#ifdef DEBUG_TREE +// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s()); + if (to_return->branches()) LOG("pushing 0") + else LOG("switching direction"); +#endif + if (to_return->branches()) + push(to_return->branch(0)); + else + _aim = TOWARD_ROOT; + } else { + // going up means that we need to get rid of some things before we + // start seeing new nodes again. + if (size() == 1) return false; + // the last node has been seen.... + tree *last = (tree *)pop(); + tree *current_tree = (tree *)current(); + int lastnum = current_tree->which(last); +#ifdef DEBUG_TREE + if (lastnum < current_tree->branches() - 1) + LOG(a_sprintf("going down %d", lastnum+1)) + else LOG("still going up"); +#endif + if (lastnum < current_tree->branches() - 1) { + _aim = AWAY_FROM_ROOT; + push(current_tree->branch(lastnum + 1)); + } // else still going up. + } + break; + } + case infix: { + if (_aim == AWAY_FROM_ROOT) { + // going down means starting on the left branch. + tree *temp = (tree *)current(); +#ifdef DEBUG_TREE + if (temp->branches()) LOG("pushing 0") + else LOG("switching direction"); +#endif + if (temp->branches()) push(temp->branch(0)); + else { + _aim = TOWARD_ROOT; + to_return = (tree *)current(); +#ifdef DEBUG_TREE +// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s())); +#endif + } + } else { + // going up means that the left branch is done and we need to either + // keep going up or go down the right branch. + if (size() == 1) return false; + // the last node has been seen.... + tree *last = (tree *)pop(); + tree *current_tree = (tree *)current(); + int lastnum = current_tree->which(last); +#ifdef DEBUG_TREE + if (lastnum < 1) LOG(a_sprintf("going down %d", lastnum+1)) + else LOG("still going up"); +#endif + if (lastnum < 1) { + _aim = AWAY_FROM_ROOT; + to_return = (tree *)current(); +#ifdef DEBUG_TREE +/// LOG(a_sprintf("[%s] ", to_return->get_contents()->held().s())); +#endif + push(current_tree->branch(lastnum + 1)); + } // else still going up. + } + break; + } + case to_branches: { + if (_aim == TOWARD_ROOT) return false; + else { + if (size() == 1) { + tree *temp = (tree *)current(); + if (!temp->branches()) + _aim = TOWARD_ROOT; + else + push(temp->branch(0)); + } else { + tree *last = (tree *)pop(); + tree *curr = (tree *)current(); + int lastnum = curr->which(last); + if (lastnum < curr->branches() - 1) + push(curr->branch(lastnum + 1)); + else _aim = TOWARD_ROOT; + to_return = last; + } + } + break; + } + case reverse_branches: { + if (_aim == TOWARD_ROOT) return false; + else { + if (size() == 1) { + tree *temp = (tree *)current(); + if (!temp->branches()) _aim = TOWARD_ROOT; + else push(temp->branch(temp->branches() - 1)); + } else { + tree *last = (tree *)pop(); + tree *curr = (tree *)current(); + int lastnum = curr->which(last); + if (lastnum > 0) push(curr->branch(lastnum - 1)); + else _aim = TOWARD_ROOT; + to_return = last; + } + } + break; + } + default: // intentional fall-through to postfix. + case postfix: { + if (_aim == AWAY_FROM_ROOT) { + // going down means that the bottom is still being sought. + tree *temp = (tree *)current(); +#ifdef DEBUG_TREE + if (temp->branches()) LOG("pushing 0") + else LOG("switching direction"); +#endif + if (temp->branches()) push(temp->branch(0)); + else _aim = TOWARD_ROOT; + } else { + // going up means that all nodes below current have been hit. + if (!size()) return false; // the last node has been seen... + else if (size() == 1) { + to_return = (tree *)pop(); + // this is the last node. + return true; + } + tree *last = (tree *)pop(); + to_return = last; +#ifdef DEBUG_TREE +/// LOG(a_sprintf("[%s] ", to_return->get_contents()->held())); +#endif + tree *current_tree = (tree *)current(); + int lastnum = current_tree->which(last); +#ifdef DEBUG_TREE + if (lastnum < current_tree->branches() - 1) + LOG(a_sprintf("going down %d", lastnum+1)) + else LOG("still going up"); +#endif + if (lastnum < current_tree->branches() - 1) { + _aim = AWAY_FROM_ROOT; + push(current_tree->branch(lastnum + 1)); + } // else still going up. + } + break; + } + } + return true; + // it is assumed that termination conditions cause a return earlier on. +} + +void tree::iterator::whack(tree *to_whack) +{ +#ifdef DEBUG_TREE + FUNCDEF("whack"); +#endif + if (!to_whack) return; // that's a bad goof. + if (size()) { + if (to_whack == current()) { + // we found that this is the one at the top right now. + pop(); +#ifdef DEBUG_TREE + LOG("found node in current top; removing it there."); +#endif + } else if (to_whack->parent() == current()) { + // the parent is the current top. make sure we don't mess up traversal. +#ifdef DEBUG_TREE + LOG("found node's parent as current top; don't know what to do."); +#endif + } else { +#ifdef DEBUG_TREE + LOG("found no match for either node to remove or parent in path."); +#endif + } + } + tree *parent = to_whack->parent(); + if (!parent) { +#ifdef DEBUG_TREE + LOG("no parent node for the one to whack! would have whacked " + "root of tree!"); +#endif + } else { + parent->prune(to_whack); + WHACK(to_whack); + } +} + +tree *tree::iterator::next() +{ +#ifdef DEBUG_TREE + FUNCDEF("next"); +#endif + tree *to_return = NIL; + bool found_tree = false; + while (!found_tree) { + bool still_running = next_node(to_return); + if (to_return || !still_running) found_tree = true; + } + return to_return; +} + +////////////// + +// tree methods: + +tree::tree() +: node(1) +{ set_link(BACKWARDS_BRANCH, NIL); } + +tree::~tree() +{ + // must at least unhook ourselves from the parent so we don't become a lost + // cousin. + tree *my_parent = parent(); + if (my_parent) my_parent->prune(this); + + // iterate over the child nodes and whack each individually. + while (branches()) delete branch(0); + // this relies on the child getting out of our branch list. +} + +tree *tree::parent() const { return (tree *)get_link(BACKWARDS_BRANCH); } + +int tree::branches() const { return links() - 1; } + +tree *tree::branch(int branch_number) const +{ + branch_number++; + bounds_return(branch_number, 1, branches(), NIL); + return (tree *)get_link(branch_number); +} + +int tree::which(tree *branch_to_find) const +{ return node::which((node *)branch_to_find) - 1; } + +tree *tree::root() const +{ + const tree *traveler = this; + // keep looking at the backwards branch until it is a NIL. the tree with + // a NIL BACKWARDS_BRANCH is the root. return that tree. + while (traveler->get_link(BACKWARDS_BRANCH)) + traveler = (tree *)traveler->get_link(BACKWARDS_BRANCH); + return const_cast(traveler); +} + +void tree::attach(tree *new_branch) +{ + if (!new_branch) return; + insert_link(links(), new_branch); + new_branch->set_link(BACKWARDS_BRANCH, this); +} + +void tree::insert(int branch_place, tree *new_branch) +{ + branch_place++; + insert_link(links(), NIL); + if (branch_place >= links()) + branch_place = links() - 1; + for (int i = links() - 1; i > branch_place; i--) + set_link(i, get_link(i-1)); + set_link(branch_place, new_branch); + new_branch->set_link(BACKWARDS_BRANCH, this); +} + +outcome tree::prune(tree *branch_to_cut) +{ + int branch_number = which(branch_to_cut); + if (branch_number == basis::common::NOT_FOUND) return basis::common::NOT_FOUND; + return prune_index(branch_number); +} + +outcome tree::prune_index(int branch_to_cut) +{ + branch_to_cut++; + bounds_return(branch_to_cut, 1, branches(), basis::common::NOT_FOUND); + tree *that_branch = (tree *)get_link(branch_to_cut); + that_branch->set_link(BACKWARDS_BRANCH, NIL); + zap_link(branch_to_cut); + return basis::common::OKAY; +} + +int tree::depth() const +{ + tree *my_root = root(); + const tree *current_branch = this; + int deep = 0; + while (current_branch != my_root) { + current_branch = current_branch->parent(); + deep++; + } + return deep; +} + +//probably okay; we want to use this function rather than non-existent +//node base function which isn't implemented yet. +bool tree::generate_path(path &to_follow) const +{ +if (to_follow.size()) {} +/* + tree *traveller = this; + path to_accumulate(root()); + while (traveller->parent() != NIL) { +// int branch_number = traveller->parent()->which(traveller); +// if (branch_number == BRANCH_NOT_FOUND) non_continuable_error +// (class_name(), "generate_path", "branch not found during path construction"); +// chunk *to_stuff = new chunk +// (SELF_OWNED, (byte *)&branch_number, sizeof(int)); + to_accumulate.push(traveller); + traveller = traveller->parent(); + } + // the order of things on the stack needs to be reversed now. +// path to_return = new stack(*to_accumulate.invert()); +// return to_return; + to_accumulate.invert(); + return to_accumulate; +*/ +return false;//temp. +} + +//hmmm: need text form! + +tree::iterator tree::start(traversal_directions direction) const +{ return iterator(this, direction); } + +} // namespace. + diff --git a/nucleus/library/nodes/tree.h b/nucleus/library/nodes/tree.h new file mode 100644 index 00000000..31d0a99f --- /dev/null +++ b/nucleus/library/nodes/tree.h @@ -0,0 +1,164 @@ +#ifndef TREE_CLASS +#define TREE_CLASS + +/*****************************************************************************\ +* * +* Name : tree * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "node.h" +#include "path.h" + +#include + +namespace nodes { + +//! A dynamically linked tree with an arbitrary number of branches. +/*! + A tree is defined as a node with n branch nodes, where n is dynamic. + Each branch is also a tree. Branch numbers range from 0 through n-1 in + the methods below. Trees can be self-cleaning, meaning that the tree + will destroy all of its children when it is destroyed. + + NOTE: the node indices are not numbered completely obviously; it is better + to use the tree functions for manipulating the node rather than + muddling with it directly. the branch to the tree node's parent is + stored as node zero, in actuality, rather than node zero being the + first branch. +*/ + +class tree : public node +{ +public: + tree(); + //!< constructs a new tree with a root and zero branches. + + virtual ~tree(); + //!< destroys the tree by recursively destroying all child tree nodes. + + DEFINE_CLASS_NAME("tree"); + + virtual tree *branch(int branch_number) const; + //!< Returns the specified branch of this tree. + /*!< NIL is returned if the "branch_number" refers to a branch that + does not exist. */ + + virtual int which(tree *branch_to_find) const; + //!< Returns the branch number for a particular branch in this tree. + /*!< common::NOT_FOUND if the branch is not one of the child nodes. */ + + virtual int branches() const; + //!< Returns the number of branches currently connected to this tree. + + virtual tree *parent() const; + //!< Returns the tree node that is the immediate ancestor of this one. + /*!< if this is the root node, then NIL is returned. */ + + virtual tree *root() const; + //!< Locates and returns the absolute root of the tree containing this tree. + /*!< If this tree IS the absolute root, then "this" is returned. */ + + virtual int depth() const; + //!< Returns the distance of "this" from the root. The root's depth is 0. + + virtual void attach(tree *new_branch); + //!< Attaches the specified branch to the current tree. + + virtual void insert(int branch_place, tree *new_branch); + //!< inserts "new_branch" before the branches starting at "branch_place". + /*!< Places a branch at a particular index and pushes the branches at that + index (and after it) over by one branch. */ + + virtual basis::outcome prune(tree *branch_to_cut); + //!< Removes the specified branch from this tree. + /*!< note that the pruning does not affect the branch being removed; it + just detaches that branch from the tree. if one wants to get rid of the + branch, it should be deleted. if this cannot find the tree specified in + the available branches then the branches of this tree are not touched and + common::NOT_FOUND is returned. */ + virtual basis::outcome prune_index(int branch_to_cut); + //!< Removes the branch at the specified index from this tree. + /*!< if this is given an invalid branch number, then the available + branches then the branches of this tree are not touched and + common::NOT_FOUND is returned. */ + + enum traversal_directions { prefix, infix, postfix, to_branches, + reverse_branches }; + //!< these are the different kinds of tree traversal that are supported. + /*!< "prefix" means that tree nodes will be visited as soon as they are + seen; the deepest nodes have to wait the longest to be seen by the + traversal. "postfix" means that tree nodes are not visited until all of + their ancestors have been visited; the nodes nearer the start of traversal + will wait the longest to be visited. the "infix" direction visits + the left branch, then the starting node, then the right branch. + "infix" is only valid for binary or unary trees; it is an error to + apply "infix" to a tree containing a node that has more than 2 branches. + the direction "to_branches" visits each of the branches in the order + defined by the branch() method. "to_branches" does not visit this + tree's node. "reverse_branches" operates in the opposite direction + of traversal from "to_branches". "reverse_branches" also does not + visit this node. "reverse_branches" can be used to prune off subtrees + during iteration without changing the ordering of the branches; this + is valuable because a pruning operation applied in "to_branches" order + would keep reducing the index of the branches. */ + + enum elevator_directions { TOWARD_ROOT, AWAY_FROM_ROOT }; + //!< movement in the tree is either towards or away from the root. + /*!< distinguishes between motion towards the root node of the tree and + motion away from the root (towards one's children). */ + + class iterator : public path + { + public: + iterator(const tree *initial, traversal_directions direction); + ~iterator(); + + tree *next(); + //!< Returns a pointer to the next tree in the direction of traversal. + /*!< If the traversal is finished, NIL is returned. */ + + void whack(tree *to_whack); + //!< destroys the tree "to_whack". + /*!< whacks the node "to_whack" by patching this iterator so that future + iterations will be correct. it is required that the "to_whack" node + was just returned from a call to next(). + NOTE: this has only been tested with postfix so far. */ + + traversal_directions _order; + elevator_directions _aim; + + private: + bool next_node(tree *&to_return); + //!< sets "to_return" to the next tree in the direction of tree traversal. + /*!< if the next node could not be found in one invocation of next_node, + then "to_return" is set to NIL. the function returns a boolean which + is true only if the iteration process can be continued by another call + to next_node. if the function returns false, the iteration is + complete and "to_return" will always be NIL. */ + }; + + iterator start(traversal_directions direction) const; + //!< Returns a fresh iterator positioned at this tree node. + + virtual bool generate_path(path &to_follow) const; + //!< Returns the path to "this" path_tree from its root. + +private: + // unavailable. + tree(const tree &); + tree &operator =(const tree &); +}; + +} // namespace. + +#endif + diff --git a/nucleus/library/processes/configured_applications.cpp b/nucleus/library/processes/configured_applications.cpp new file mode 100644 index 00000000..1c8e4617 --- /dev/null +++ b/nucleus/library/processes/configured_applications.cpp @@ -0,0 +1,326 @@ +/*****************************************************************************\ +* * +* Name : configured_applications +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000 By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "configured_applications.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace structures; +using namespace textual; + +namespace processes { + +//#define DEBUG_APP_CONFIG + // uncomment for noisier debugging version. + +#undef LOG +#define LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) + +////////////// + +const char *PRODUCT_HEADING() { return "product"; } + // the string used for our startup entries as a prefix to the product. + +const char *ASSIGN_TOKEN() { return "="; } + // how we distinguish the key from the value for startup entries. + +const char *SEPARATOR_TOKEN() { return ","; } + // the character between separate key/value pairs in the startup string. + +const char *SEPARATOR_TEXT() { return ", "; } + // the string we use for the separator when printing it. + +const char *PARMS_HEADING() { return "parms"; } + // the tag for parameters in the startup entry. + +const char *ONESHOT_HEADING() { return "oneshot"; } + // the key name for startup entries' flag for once only execution. + +////////////// + +configured_applications::configured_applications(const astring &config_file, + const astring &basename) +: _lock(new mutex), + _config(new ini_configurator(config_file, ini_configurator::RETURN_ONLY, + ini_configurator::APPLICATION_DIRECTORY)), + _sector(new section_manager(*_config, astring(basename) + "_TOC", + astring(PRODUCT_HEADING()) + "_")) +{ +// FUNCDEF("constructor"); + string_table startup_info; + if (!find_section(STARTUP_SECTION(), startup_info)) { + // if there's no startup section, we do nothing right now. + LOG(astring("the startup section doesn't exist yet; adding it now.")); + astring entry = make_startup_entry(basename, "", false); + startup_info.add(STARTUP_APP_NAME(), entry); + add_section(STARTUP_SECTION(), startup_info); + } +} + +configured_applications::~configured_applications() +{ + WHACK(_sector); + WHACK(_config); + WHACK(_lock); +} + +const char *configured_applications::STARTUP_SECTION() +{ return "PRIVATE_STARTUP_LNCH1.0"; } + +const char *configured_applications::STARTUP_APP_NAME() +{ return "placeholder"; } + +bool configured_applications::product_exists(const astring &product) +{ + auto_synchronizer l(*_lock); + if (!_sector->section_exists(product)) return false; + return true; +} + +astring configured_applications::find_program(const astring &product, + const astring &app_name, int &level) +{ + auto_synchronizer l(*_lock); + astring heading = _sector->make_section_heading(product); + astring found = _sector->config().load(heading, app_name, ""); +////////////// +//overly specific bits here... +//hmmm: add this in as a specialization provided by real owner of class. + if (!found) { + // we didn't find the entry under the section we wanted to find it in. + // there are a couple cases where we can kludge this section to a + // different name, based on legacy requirements, and still find the + // right item possibly. + if (product.iequals("supervisor")) { + // for some older installs, they say "supervisor" but mean "core". + heading = _sector->make_section_heading("core"); + found = _sector->config().load(heading, app_name, ""); + } else if (product.iequals("lightlink")) { + heading = _sector->make_section_heading("core"); + found = _sector->config().load(heading, app_name, ""); + if (!found) { + // we can take one more remedial step for this phrase. + heading = _sector->make_section_heading("server"); + found = _sector->config().load(heading, app_name, ""); + } + } + } +//end of overly specific. +////////////// + found = parser_bits::substitute_env_vars(found); + + int comma_loc = found.find(","); + if (negative(comma_loc)) return ""; // couldn't find our priority. + level = found.convert(0); + found.zap(0, comma_loc); + + return found; +} + +bool configured_applications::add_program(const astring &product, + const astring &app_name, const astring &full_path, int level) +{ +#ifdef DEBUG_APP_CONFIG + FUNCDEF("add_program"); +#endif + auto_synchronizer l(*_lock); + bool existed = true; + // lookup the section, if it exists. + string_table info_table; + if (!_sector->section_exists(product)) { + existed = false; + } else + find_section(product, info_table); +#ifdef DEBUG_APP_CONFIG + if (existed) { + LOG(astring("section for ") + product + " found:"); + for (int i = 0; i < info_table.symbols(); i++) + LOG(astring("key=") + info_table.name(i) + " value=" + info_table[i]); + } else LOG(astring("section for ") + product + " not found."); +#endif + // remove any existing entry. + info_table.whack(app_name); + // plug in our new entry. + a_sprintf full_entry("%d,%s", level, full_path.s()); + info_table.add(app_name, full_entry); +#ifdef DEBUG_APP_CONFIG + LOG(astring("new section for ") + product + " has:"); + for (int i = 0; i < info_table.symbols(); i++) + LOG(astring("key=") + info_table.name(i) + " value=" + info_table[i]); +#endif + // now call the proper storage function based on whether the section + // existed before or not. + if (existed) return replace_section(product, info_table); + else return add_section(product, info_table); +} + +bool configured_applications::remove_program(const astring &product, + const astring &app_name) +{ +// FUNCDEF("remove_program"); + auto_synchronizer l(*_lock); + // if the section's missing, there's nothing to remove... + string_table info_table; + if (!find_section(product, info_table)) return true; + // the section did exist, so remove any existing entry. + info_table.whack(app_name); + // now call the proper storage function based on whether the section + // existed before or not. + return replace_section(product, info_table); +} + +bool configured_applications::find_section(const astring §ion_name, + string_table &info_found) +{ +// FUNCDEF("find_section"); + info_found.reset(); + auto_synchronizer l(*_lock); + if (!_sector->find_section(section_name, info_found)) { + LOG(section_name + " was not found in the configuration."); + return false; + } + return true; +} + +bool configured_applications::add_section(const astring §ion_name, + const string_table &info_found) +{ + auto_synchronizer l(*_lock); + return _sector->add_section(section_name, info_found); +} + +bool configured_applications::replace_section(const astring §ion_name, + const string_table &info_found) +{ + auto_synchronizer l(*_lock); + return _sector->replace_section(section_name, info_found); +} + +astring configured_applications::make_startup_entry(const astring &product, + const astring &parms, bool one_shot) +{ + return astring(PRODUCT_HEADING()) + ASSIGN_TOKEN() + product + + SEPARATOR_TEXT() + PARMS_HEADING() + ASSIGN_TOKEN() + + parms + SEPARATOR_TEXT() + ONESHOT_HEADING() + ASSIGN_TOKEN() + + astring(astring::SPRINTF, "%d", one_shot); +} + +bool configured_applications::parse_startup_entry(const astring &info, + astring &product, astring &parms, bool &one_shot) +{ +// FUNCDEF("parse_startup_section"); + // parse the items that are in the entry for this program. + variable_tokenizer entry_parser(SEPARATOR_TOKEN(), ASSIGN_TOKEN()); + entry_parser.parse(info); + // grab the pertinent bits for the program to be started. + product = entry_parser.find(PRODUCT_HEADING()); + parms = entry_parser.find(PARMS_HEADING()); +//LOG(astring("parms=") + parms); + astring once = entry_parser.find(ONESHOT_HEADING()); + one_shot = (bool)once.convert(0); + // we require the product part at least. + if (!product) return false; + return true; +} + +bool configured_applications::find_entry(const string_table &table, + const astring &name, astring &location) +{ + // seek the entry in the table specified. + astring *found = table.find(name); + if (!found) return false; + // found the entry using the name. + location = *found; + return true; +} + +bool configured_applications::add_startup_entry(const astring &product, + const astring &app_name, const astring ¶meters, int one_shot) +{ +// FUNCDEF("add_startup_entry"); + auto_synchronizer l(*_lock); + + LOG(astring("product \"") + product + "\", application \"" + app_name + + (one_shot? astring("\", OneShot") : astring("\", MultiUse"))); + + string_table startup_info; + if (!find_section(STARTUP_SECTION(), startup_info)) { + // if there's no startup section, we can't go on. that should have been + // created during startup of this program. + LOG(astring("internal startup section not found!")); + return false; + } + + astring new_entry = make_startup_entry(product, parameters, + one_shot); + startup_info.add(app_name, new_entry); + if (!replace_section(STARTUP_SECTION(), startup_info)) + return false; +//hmmm: that's a bogus error; this is really an internal fup error. + + return true; +} + +bool configured_applications::remove_startup_entry(const astring &product, + const astring &app_name) +{ +// FUNCDEF("remove_startup_entry"); + auto_synchronizer l(*_lock); + + LOG(astring("product \"") + product + "\", application \"" + app_name + "\""); + + string_table startup_info; + if (!find_section(STARTUP_SECTION(), startup_info)) { + // if there's no startup section, we try to add one. + add_section(STARTUP_SECTION(), startup_info); + // if it still doesn't exist afterwards, we're hosed. + if (!find_section(STARTUP_SECTION(), startup_info)) { +/// COMPLAIN_PRODUCT; +//massive fup of some unanticipated sort. +//complain. + return false; + } + } + + // check that the entry already exists for this program. + astring entry_found; + if (!find_entry(startup_info, app_name, entry_found)) { +// COMPLAIN_APPLICATION; + LOG(astring("no entry was found for ") + app_name); + return false; + } + + startup_info.whack(app_name); + if (!replace_section(STARTUP_SECTION(), startup_info)) { +//what happened with that? + return false; + } + + return true; +} + +} //namespace. + + diff --git a/nucleus/library/processes/configured_applications.h b/nucleus/library/processes/configured_applications.h new file mode 100644 index 00000000..d7c6f776 --- /dev/null +++ b/nucleus/library/processes/configured_applications.h @@ -0,0 +1,123 @@ +#ifndef CONFIGURED_APPLICATIONS_CLASS +#define CONFIGURED_APPLICATIONS_CLASS + +/*****************************************************************************\ +* * +* Name : configured_applications +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 2000 By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +namespace processes { + +//! Manages the initialization file for a set of registered applications. +/*! + This records the list of programs that are allowed to be executed as well + as the list of applications launched at object startup time. +*/ + +class configured_applications +{ +public: + configured_applications(const basis::astring &config_file, const basis::astring &basename); + //!< manages application settings for in the "config_file". + /*!< the "basename" is used for the section name of a list of products + that can be managed by this class. each product has a set of applications + that are part of the product's full package. */ + + virtual ~configured_applications(); + + // this section has mainly informational functions. + + DEFINE_CLASS_NAME("configured_applications"); + + static const char *STARTUP_SECTION(); + //!< the section where startup info is stored. + + static const char *STARTUP_APP_NAME(); + //!< a special placeholder name that will appear in the startup list. + /*!< it is not to be executed like the other programs for startup. */ + + static bool find_entry(const structures::string_table &table, const basis::astring &name, + basis::astring &location); + //!< returns true if the key "name" for a program is found in the "table". + /*!< the "location" is set to the value found in the table if successful. + */ + + static basis::astring make_startup_entry(const basis::astring &product, + const basis::astring &parms, bool one_shot); + //!< returns the appropriate string for a startup record. + + static bool parse_startup_entry(const basis::astring &info, basis::astring &product, + basis::astring &parms, bool &one_shot); + //!< processes the items in "info" as an application startup list. + /*!< using a string "info" that was listed as the startup entry for an + application, the "product", "parms" and "one_shot" bits are parsed out + and returned. */ + + bool product_exists(const basis::astring &product); + //!< returns true if the section for "product" exists in the TOC. + + basis::astring find_program(const basis::astring &product, const basis::astring &app_name, + int &level); + //!< seeks out the entry for the "product" and "app_name" in our info. + /*!< the returned string will either be empty (on failure) or will contain + the full path to the application (on success). the "level" will specify + the ordering of shutdown, where larger levels are shut down first. */ + + // the following functions actually modify the configuration file. + + bool find_section(const basis::astring §ion_name, structures::string_table &info_found); + //!< locates the entries for "section_name" and stores them in "info_found". + + bool add_section(const basis::astring §ion_name, const structures::string_table &info); + //!< puts a chunk of "info" into the section for "section_name". + /*!< this fails if the section already exists. */ + + bool replace_section(const basis::astring §ion_name, const structures::string_table &info); + //!< replaces the section for "section_name" with "info". + /*!< this fails if the section does not already exist. */ + + bool add_program(const basis::astring &product, const basis::astring &app_name, + const basis::astring &full_path, int level); + //!< registers a program "app_name" into the "product" section. + /*!< the "full_path" specifies where to find the program and the "level" + gives the application an ordering for shutdown. higher levels are shut + down before lower ones. */ + + bool remove_program(const basis::astring &product, const basis::astring &app_name); + //!< takes a previously registered "app_name" out of the list for "product". + + bool add_startup_entry(const basis::astring &product, const basis::astring &app_name, + const basis::astring ¶meters, int one_shot); + //!< establishes the "app_name" as a program launched at object startup. + /*!< adds an entry to the startup section for a program that will be + launched when the application manager restarts. */ + + bool remove_startup_entry(const basis::astring &product, const basis::astring &app_name); + //!< takes an existing entry for the "app_name" out of the startup section. + +private: + basis::mutex *_lock; //!< synchronization protection for our objects. + configuration::ini_configurator *_config; //!< manages our configuration settings. + configuration::section_manager *_sector; //!< keeps track of our product sections. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/ethread.cpp b/nucleus/library/processes/ethread.cpp new file mode 100644 index 00000000..30efcc88 --- /dev/null +++ b/nucleus/library/processes/ethread.cpp @@ -0,0 +1,328 @@ +/*****************************************************************************\ +* * +* Name : ethread * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ethread.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#elif defined(__UNIX__) + #include +#else + #error unknown OS for thread support. +#endif + +using namespace basis; +using namespace loggers; +using namespace structures; +using namespace timely; + +//#define COUNT_THREADS + // if this is enabled, then threads will be counted when they are created + // or destroyed. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +namespace processes { + +const int MAXIMUM_CREATE_ATTEMPTS = 20; + // the number of retries we allow to try creating a thread, if the first + // attempt fails. + +const int MINIMUM_SLEEP_PERIOD = 10; + // this is the smallest time we'll sleep for if we're slack. + +const int MAXIMUM_SLEEP_PERIOD = 200; + // the number of milliseconds we use for breaking up longer sleep periods. + +const int SNOOZE_FOR_RETRY = 100; + // how long to sleep when a thread creation fails. + +#ifdef COUNT_THREADS + // singleton thread counter code. + class thread_counter : public virtual root_object { + public: + thread_counter() : _count(0) {} + DEFINE_CLASS_NAME("thread_counter"); + void increment() { + auto_synchronizer l(_lock); + _count++; + } + void decrement() { + auto_synchronizer l(_lock); + _count--; + } + private: + int _count; + mutex _lock; + }; + + SAFE_STATIC(thread_counter, _current_threads, ) + +//hmmm: this seems to not be used anywhere yet. it needs to be accessible +// externally if it's going to serve any useful purpose. + +#endif + +ethread::ethread() +: _thread_ready(false), + _thread_active(false), + _stop_thread(false), + _data(NIL), +#ifdef __UNIX__ + _handle(new pthread_t), +#elif defined(__WIN32__) + _handle(0), +#endif + _sleep_time(0), + _periodic(false), + _next_activation(new time_stamp), + _how(TIGHT_INTERVAL) // unused. +{ +// FUNCDEF("constructor [one-shot]"); +} + +ethread::ethread(int sleep_timer, timed_thread_types how) +: _thread_ready(false), + _thread_active(false), + _stop_thread(false), + _data(NIL), +#ifdef __UNIX__ + _handle(new pthread_t), +#elif defined(__WIN32__) + _handle(0), +#endif + _sleep_time(sleep_timer), + _periodic(true), + _next_activation(new time_stamp), + _how(how) +{ +// FUNCDEF("constructor [periodic]"); + if (sleep_timer < MINIMUM_SLEEP_PERIOD) { + _sleep_time = MINIMUM_SLEEP_PERIOD; + } +} + +ethread::~ethread() +{ + stop(); + WHACK(_next_activation); +#ifdef __UNIX__ + WHACK(_handle); +#endif +} + +///void ethread::pre_thread() {} + +///void ethread::post_thread() {} + +// the reschedule operation assumes that assignment to a time stamp +// object (based on a real numbers) happens indivisibly. +void ethread::reschedule(int delay) +{ + *_next_activation = time_stamp(delay); // start after the delay. +} + +bool ethread::start(void *thread_data) +{ + FUNCDEF("start"); + if (!thread_finished()) return false; // already running. + _data = thread_data; // store the thread's data pointer. + _stop_thread = false; // don't stop now. + _thread_ready = true; // we're starting it now. + _next_activation->reset(); // make "now" the next time to activate. + bool success = false; + int error = 0; + int attempts = 0; + while (attempts++ < MAXIMUM_CREATE_ATTEMPTS) { +#ifdef __UNIX__ + pthread_attr_t attribs; // special flags for creation of thread. + int aret = pthread_attr_init(&attribs); + if (aret) LOG("failed to init attribs."); + aret = pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED); + if (aret) LOG("failed to set detach state."); + int ret = -1; + if (_periodic) + ret = pthread_create(_handle, &attribs, periodic_thread_driver, + (void *)this); + else + ret = pthread_create(_handle, &attribs, one_shot_thread_driver, + (void *)this); + if (!ret) success = true; + else error = ret; +#elif defined(__WIN32__) + if (_periodic) + _handle = _beginthread(periodic_thread_driver, 0, (void *)this); + else + _handle = _beginthread(one_shot_thread_driver, 0, (void *)this); + if (_handle != -1) success = true; + else error = critical_events::system_error(); +#endif + if (success) break; // got it created. + LOG("failed to create thread; trying again..."); + time_control::sleep_ms(SNOOZE_FOR_RETRY); + } + if (!success) { + // couldn't start it, so reset our state. + LOG(astring("failed to create thread, error is ") + + critical_events::system_error_text(error)); + exempt_stop(); + return false; + } + return true; +} + +void ethread::stop() +{ + cancel(); // tell thread to leave. + if (!thread_started()) return; // not running. + while (!thread_finished()) { +#ifdef __WIN32__ + int result = 0; + if (!GetExitCodeThread((HANDLE)_handle, (LPDWORD)&result) + || (result != STILL_ACTIVE)) { + exempt_stop(); + break; + } +#endif + time_control::sleep_ms(10); // wait for thread to leave. + } +} + +void ethread::exempt_stop() +{ + _thread_active = false; + _thread_ready = false; +#ifdef __WIN32__ + _handle = 0; +#endif +} + +#ifdef __UNIX__ +void *ethread::one_shot_thread_driver(void *hidden_pointer) +#elif defined(__WIN32__) +void ethread::one_shot_thread_driver(void *hidden_pointer) +#else +#error unknown thread signature. +#endif +{ +// FUNCDEF("one_shot_thread_driver"); + ethread *manager = (ethread *)hidden_pointer; +#ifdef __UNIX__ + if (!manager) return NIL; +#else + if (!manager) return; +#endif +#ifdef COUNT_THREADS + _current_threads().increment(); +#endif +/// manager->pre_thread(); + manager->_thread_active = true; + manager->perform_activity(manager->_data); +/// manager->post_thread(); + manager->exempt_stop(); +#ifdef COUNT_THREADS + _current_threads().decrement(); +#endif +#ifdef __UNIX__ + pthread_exit(NIL); + return NIL; +#else + _endthread(); +#endif +} + +#ifdef __UNIX__ +void *ethread::periodic_thread_driver(void *hidden_pointer) +#elif defined(__WIN32__) +void ethread::periodic_thread_driver(void *hidden_pointer) +#else +#error unknown thread signature. +#endif +{ +// FUNCDEF("periodic_thread_driver"); + ethread *manager = (ethread *)hidden_pointer; +#ifdef __UNIX__ + if (!manager) return NIL; +#elif defined(__WIN32__) + if (!manager) return; +#endif +#ifdef COUNT_THREADS + _current_threads().increment(); +#endif +/// manager->pre_thread(); + + while (!manager->_stop_thread) { + // for TIGHT_INTERVAL, we reset the next active time here. this is safe + // relative to the reschedule() method, since we're about to do + // perform_activity() right now anyway. this brings about a pretty hard + // interval; if perform_activity() takes N milliseconds, then there will + // only be sleep_time - N (min zero) ms before the next invocation. + if (manager->_how == TIGHT_INTERVAL) + *manager->_next_activation = time_stamp(manager->_sleep_time); + + manager->_thread_active = true; + manager->perform_activity(manager->_data); + manager->_thread_active = false; + + // SLACK_INTERVAL means between activations. we reset the next activation + // here to ensure we wait the period specified for sleep time, including + // whatever time was taken for the activity itself. + if (manager->_how == SLACK_INTERVAL) + *manager->_next_activation = time_stamp(manager->_sleep_time); + + // we do the sleep timing in chunks so that there's not such a huge wait + // when the user stops the thread before the sleep interval elapses. + // snooze until time for the next activation. + while (!manager->_stop_thread) { + int time_diff = int(manager->_next_activation->value() + - time_stamp().value()); + if (time_diff < 0) time_diff = 0; // time keeps on slipping. + // make sure we take our time if we're slack intervalled. + if (manager->_how == SLACK_INTERVAL) { + if (time_diff < MINIMUM_SLEEP_PERIOD) + time_diff = MINIMUM_SLEEP_PERIOD; + } + if (time_diff > MAXIMUM_SLEEP_PERIOD) + time_diff = MAXIMUM_SLEEP_PERIOD; + if (!manager->_stop_thread) + time_control::sleep_ms(time_diff); + if (time_stamp() >= *manager->_next_activation) + break; + } + } +/// manager->post_thread(); + manager->exempt_stop(); +#ifdef COUNT_THREADS + _current_threads().decrement(); +#endif +#ifdef __UNIX__ + pthread_exit(NIL); + return NIL; +#elif defined(__WIN32__) + _endthread(); +#endif +} + +} //namespace. + diff --git a/nucleus/library/processes/ethread.h b/nucleus/library/processes/ethread.h new file mode 100644 index 00000000..9adab465 --- /dev/null +++ b/nucleus/library/processes/ethread.h @@ -0,0 +1,184 @@ +#ifndef ETHREAD_CLASS +#define ETHREAD_CLASS + +/*****************************************************************************\ +* * +* Name : ethread (easy thread) * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +#include + +#ifndef __APPLE__ +#ifdef __UNIX__ +// typedef long unsigned int pthread_t; +#endif +#endif + +namespace processes { + +//! Provides a platform-independent object for adding threads to a program. +/*! + This greatly simplifies creating and managing threads by hiding all the + operating system details. The user just needs to override one virtual + function in their derived object to perform the main activity of their + thread. The thread can be a one time invocation or it can run periodically. + Control over the thread remains in the hands of the program that started + it. +*/ + +class ethread : public virtual basis::root_object +{ +public: + ethread(); + //!< creates a single-shot thread object. + /*!< the OS-level thread is not started until the start() method is + invoked. this constructor creates a thread that will only execute + once; when start() is called, the thread starts up and performs its + activity. it will then stop. to run it again, start() must be invoked + again. however, if the perform_activity() method just keeps running, + then the single-shot thread can live as long as needed. it is important + for such a thread to periodically check should_exit() to avoid having + the program hang-up when it's supposed to be shutting down. */ + + enum timed_thread_types { TIGHT_INTERVAL, SLACK_INTERVAL }; + + ethread(int sleep_timer, timed_thread_types how = SLACK_INTERVAL); + //!< creates a managed thread object that runs on a periodic interval. + /*!< the thread will activate every "sleep_timer" milliseconds. when + start() is invoked, the thread's action (via the perform_activity() + method) will be performed at regular intervals (using the specified value + for "sleep_timer"). the thread will continue activating until the stop() + method is called. a faster interval is used internally during sleep + periods such that calling stop() will not consume the whole "sleep_timer" + period. if the "how" is TIGHT_INTERVAL, then the thread will activate + every "sleep_timer" milliseconds, as accurately as possible. if the "how" + is SLACK_INTERVAL, then the thread will activate after a delay of + "sleep_timer" milliseconds from its last activation. the latter mode + allows the thread to consume its entire intended operation time knowing + that there will still be slack time between when it is active. the + former mode requires the thread to only run for some amount of time less + than its "sleep_timer"; otherwise it will hog a lot of the CPU. */ + + virtual ~ethread(); + + DEFINE_CLASS_NAME("ethread"); + + bool start(void *thread_data); + //!< causes the thread to start, if it has not already been started. + /*!< if the thread has terminated previously, then this will restart the + thread. true is returned if the thread could be started. false is + returned if the thread could not be started or if it is already running. */ + + void stop(); + //!< tells the thread to shutdown and waits for the shutdown to occur. + /*!< this will cause the OS thread to terminate once the current (if any) + perform_activity() invocation completes. the thread may be restarted + with start(). */ + + void cancel() { _stop_thread = true; } + //!< stops the thread but does not wait until it has terminated. + /*!< this is appropriate for use within the perform_activity() method. */ + +// virtual void pre_thread(); + //!< invoked just after after start(), when the OS thread is created. + /*!< the call comes in _from_ the thread itself, so the derived method + must be thread-safe. */ +// virtual void post_thread(); + //!< this is invoked just before the thread is to be terminated. + /*!< the call also comes in from the thread itself, so the implementation + must be thread-safe. */ + + virtual void perform_activity(void *thread_data) = 0; + //!< carries out the main activity of the thread. + /*!< this is called repeatedly by the main thread management function and + so should return as soon as possible. if it does not return fairly + regularly, then the thread shutdown process will not occur until the + function exits on its own. */ + + void exempt_stop(); + //!< this special form of stop() does not wait for the thread to exit. + /*!< it is required in certain weird OS situations where the thread does + not exit properly and stop() would cause an infinite wait. don't use it + unless you are SURE that this is the case. */ + + void reschedule(int delay = 0); + //!< causes a periodic thread to activate after "delay" milliseconds from now. + /*!< this resets the normal activation period, but after the next + activation occurs, the normal activation interval takes over again. */ + + int sleep_time() const { return _sleep_time; } + //!< returns the current periodic thread interval. + /*!< this is only meaningful for periodic threads. */ + + void sleep_time(int new_sleep) { _sleep_time = new_sleep; } + //!< adjusts the period for the thread to the "new_sleep" interval. + /*!< this is only meaningful for periodic threads. */ + + // these functions report on the thread state. + + bool thread_started() const { return _thread_ready; } + //!< returns true if the thread has been started. + /*!< this does not mean it is necessarily active. */ + + bool thread_finished() const { return !_thread_ready; } + //!< returns true if the thread has exited. + /*!< This can happen either by the thread responding to the stop() or + cancel() methods or when the thread stops of its own accord. if this + returns true, it means that the thread will not start up again unless + the user invokes start(). */ + + bool thread_active() const { return _thread_active; } + //!< returns true if the thread is currently performing its activity. + /*!< this information is not necessarily relevant even at the point it is + returned (because of the nature of multethreading), so don't believe this + information for anything important. */ + + bool should_stop() const { return _stop_thread; } + //!< reports whether the thread should stop right now. + /*!< this returns true due to an invocation of stop() or cancel(). */ + +private: + bool _thread_ready; //!< is the thread ready to run (or running)? + bool _thread_active; //!< is the thread currently performing? + bool _stop_thread; //!< true if the thread should stop now. + void *_data; //!< holds the thread's link back to whatever. +#ifdef __UNIX__ + pthread_t *_handle; //!< thread structure for our thread. +#elif defined(__WIN32__) + uintptr_t _handle; //!< thread handle for the active thread, or zero. +#endif + int _sleep_time; //!< threads perform at roughly this interval. + bool _periodic; //!< true if this thread should run repeatedly. + timely::time_stamp *_next_activation; //!< the next time perform_activity is called. + timed_thread_types _how; //!< how is the period evaluated? + + // the OS level thread functions. +#ifdef __UNIX__ + static void *periodic_thread_driver(void *hidden_pointer); + static void *one_shot_thread_driver(void *hidden_pointer); +#elif defined(__WIN32__) + static void periodic_thread_driver(void *hidden_pointer); + static void one_shot_thread_driver(void *hidden_pointer); +#endif + + // forbidden. + ethread(const ethread &); + ethread &operator =(const ethread &); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/heartbeat.cpp b/nucleus/library/processes/heartbeat.cpp new file mode 100644 index 00000000..7b3d9f68 --- /dev/null +++ b/nucleus/library/processes/heartbeat.cpp @@ -0,0 +1,102 @@ + + + +/*****************************************************************************\ +* * +* Name : heartbeat * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "heartbeat.h" + +#include +#include +#include + +using namespace basis; +using namespace timely; + +namespace processes { + +heartbeat::heartbeat(int misses_allowed, int check_interval) +: _next_heartbeat(new time_stamp()), + _check_interval(0), + _misses_allowed(0), + _misses(0) +{ reset(misses_allowed, check_interval); } + +heartbeat::heartbeat(const heartbeat &to_copy) +: root_object(), + _next_heartbeat(new time_stamp()), + _check_interval(0), + _misses_allowed(0), + _misses(0) +{ *this = to_copy; } + +heartbeat::~heartbeat() { WHACK(_next_heartbeat); } + +time_stamp heartbeat::heartbeat_time() const { return *_next_heartbeat; } + +bool heartbeat::due() const { return time_left() <= 0; } + +void heartbeat::made_request() { _misses++; reset_next_beat(); } + +void heartbeat::kabump() { _misses = 0; reset_next_beat(); } + +void heartbeat::reset_next_beat() +{ *_next_heartbeat = time_stamp(_check_interval); } + +int heartbeat::time_left() const +{ return int(_next_heartbeat->value() - time_stamp().value()); } + +bool heartbeat::dead() const +{ + // two cases mean the timer's dead; (1) if the misses are already too high, + // or (2) if the heartbeat is due and the misses are as many as allowed. + return (_misses > _misses_allowed) + || (due() && (_misses >= _misses_allowed)); +} + +void heartbeat::reset(int misses_allowed, int check_interval) +{ + _misses_allowed = misses_allowed; + _misses = 0; + _check_interval = check_interval; + reset_next_beat(); +} + +astring heartbeat::text_form(bool detailed) const +{ + astring to_return = (dead()? astring("expired, ") : astring("alive, ")); + to_return += (!dead() && due() ? astring("due now, ") + : astring::empty_string()); + to_return += a_sprintf("beats left=%d", misses_left()); + if (detailed) { + to_return += a_sprintf(", missed=%d, interval=%d, ", + missed_so_far(), checking_interval()); + to_return += astring("next=") + heartbeat_time().text_form(); + } + return to_return; +} + +heartbeat &heartbeat::operator =(const heartbeat &to_copy) +{ + if (this == &to_copy) return *this; + _check_interval = to_copy._check_interval; + _misses_allowed = to_copy._misses_allowed; + _misses = to_copy._misses; + *_next_heartbeat = *to_copy._next_heartbeat; + return *this; +} + +} //namespace. + + diff --git a/nucleus/library/processes/heartbeat.h b/nucleus/library/processes/heartbeat.h new file mode 100644 index 00000000..19fe3b32 --- /dev/null +++ b/nucleus/library/processes/heartbeat.h @@ -0,0 +1,114 @@ +#ifndef HEARTBEAT_CLASS +#define HEARTBEAT_CLASS + +/*****************************************************************************\ +* * +* Name : heartbeat * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace processes { + +//! Monitors a periodic heartbeat to track a resource's health. +/*! + The heartbeat is defined as a "request-and-response" based check; when the + next periodic heartbeat is due, a request is sent out. The heartbeat + request is considered successfully dealt with only if a response comes back + for the request. If the user-defined number of requests are sent without + a single response coming back, then the 'patient' is considered dead. +*/ + +class heartbeat : public virtual basis::root_object +{ +public: + heartbeat(int misses_allowed = 500, int check_interval = 10000); + //!< creates a heartbeat monitor with the specified interval and maximum skips permitted. + /*!< this allows the heartbeat request to be missed "misses_allowed" times. + the heartbeats will become due every "check_interval" milliseconds. the + defaults are a joke; you really need to set them. */ + heartbeat(const heartbeat &to_copy); + + ~heartbeat(); + + DEFINE_CLASS_NAME("heartbeat"); + + heartbeat &operator =(const heartbeat &to_copy); + + basis::astring text_form(bool detailed = false) const; + //!< returns a readable form of the heartbeat's information. + + void reset(int misses_allowed, int check_interval); + //!< retrains the heartbeat monitor for a new configuration. + + bool due() const; + //!< is the next heartbeat due yet? + + bool dead() const; + //!< is this object considered dead from missing too many heartbeats? + /*!< this is true if the heartbeat being monitored missed too many + responses to heartbeat requests. if the maximum allowed requests have + been made and there was not even a single response, then the object is + considered dead. */ + + void made_request(); + //!< records that another heartbeat request was sent out. + /*!< the time for the next heartbeat request is reset to the time between + beats. if there were already the maximum allowed number of missed + responses, then the object is now dead. */ + void need_beat() { made_request(); } + //!< a synonym for the made_request() method. + + void kabump(); + //!< registers a heartbeat response and sets the state to healthy. + /*!< this records that a heartbeat response came back from the monitored + object. after this call, there are no heartbeats recorded as missed at + all. */ + void recycle() { kabump(); } + //!< a synonym for kabump(). + + // reporting functions for internal state... + + int missed_so_far() const { return _misses; } + //!< returns the number of heartbeat responses that are pending. + int misses_left() const { return _misses_allowed - _misses; } + //!< the number of misses that this object is still allowed. + + int allowed_misses() const { return _misses_allowed; } + //!< returns the number of misses allowed overall. + int checking_interval() const { return _check_interval; } + //!< returns the period of the heartbeats. + + timely::time_stamp heartbeat_time() const; + //!< returns the time when the next heartbeat will be requested. + /*!< if no heartbeats had been missed yet, then this is the time when + the due() method starts returning true. */ + + int time_left() const; + //!< number of milliseconds left before the next beat will be requested. + /*!< if the number is zero or negative, then a heartbeat is due. */ + +private: + timely::time_stamp *_next_heartbeat; + int _check_interval; + int _misses_allowed; + int _misses; + + void reset_next_beat(); //!< resets the next_heartbeat to our interval. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/launch_process.cpp b/nucleus/library/processes/launch_process.cpp new file mode 100644 index 00000000..bb8d7ada --- /dev/null +++ b/nucleus/library/processes/launch_process.cpp @@ -0,0 +1,345 @@ + +// Name : launch_process +// Author : Chris Koeritz +/****************************************************************************** +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "launch_process.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __UNIX__ + #include + #include + #include + #include +#endif +#ifdef __WIN32__ + #include + #include + #include +#endif + +//#define DEBUG_LAUNCH_PROCESS + // uncomment for noisier debugging info. + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace structures; +using namespace timely; + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s); + +namespace processes { + +//hmmm: some of these should probably be safe statics. + +mutex &__process_synchronizer() { + static mutex __hidden_synch; + return __hidden_synch; +} + +int_set __our_kids() { + static int_set __hidden_kids; + return __hidden_kids; +} + +#ifdef __WIN32__ +bool launch_process::event_poll(MSG &message) +{ + message.hwnd = 0; + message.message = 0; + message.wParam = 0; + message.lParam = 0; + if (!PeekMessage(&message, NIL, 0, 0, PM_REMOVE)) + return false; + TranslateMessage(&message); + DispatchMessage(&message); + return true; +} +#endif + +#define SUPPORT_SHELL_EXECUTE + // if this is not commented out, then the ShellExecute version of launch_ + // -process() is available. when commented out, ShellExecute is turned off. + // disabling this support is the most common because the ShellExecute method + // in win32 was only supported for wk203 and wxp, that is only after + // windows2000 was already available. since nt and w2k don't support this, + // we just usually don't mess with it. it didn't answer a single one of our + // issues on windows vista (wfista) anyway, so it's not helping. + +//const int MAXIMUM_COMMAND_LINE = 32 * KILOBYTE; + // maximum command line that we'll deal with here. + +#ifdef __UNIX__ +void launch_process::exiting_child_signal_handler(int sig_num) +{ + FUNCDEF("exiting_child_signal_handler"); + if (sig_num != SIGCHLD) { + // uhhh, this seems wrong. + } + auto_synchronizer l(__process_synchronizer()); + for (int i = 0; i < __our_kids().length(); i++) { + int status; + pid_t exited = waitpid(__our_kids()[i], &status, WNOHANG); + if ( (exited == -1) || (exited == __our_kids()[i]) ) { + // negative one denotes an error, which we are going to assume means the + // process has exited via some other method than our wait. if the value + // is the same as the process we waited for, that means it exited. + __our_kids().zap(i, i); + i--; + } else if (exited != 0) { + // zero would be okay; this result we do not understand. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("unknown result %d waiting for process %d", exited, __our_kids()[i])); +#endif + } + } +} +#endif + +//hmmm: this doesn't seem to account for quoting properly at all? +char_star_array launch_process::break_line(astring &app, const astring ¶meters) +{ + FUNCDEF("break_line"); + char_star_array to_return; + int_array posns; + int num = 0; + // find the positions of the spaces and count them. + for (int j = 0; j < parameters.length(); j++) { + if (parameters[j] == ' ') { + num++; + posns += j; + } + } + // first, add the app name to the list of parms. + to_return += new char[app.length() + 1]; + app.stuff(to_return[0], app.length()); + int last_posn = 0; + // now add each space-separated parameter to the list. + for (int i = 0; i < num; i++) { + int len = posns[i] - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, posns[i] - 1).stuff(to_return[i + 1], len); + last_posn = posns[i] + 1; + } + // catch anything left after last separator. + if (last_posn < parameters.length() - 1) { + int len = parameters.length() - last_posn; + to_return += new char[len + 1]; + parameters.substring(last_posn, parameters.length() - 1) + .stuff(to_return[to_return.last()], len); + } + // add the sentinel to the list of strings. + to_return += NIL; +#ifdef DEBUG_LAUNCH_PROCESS + for (int q = 0; to_return[q]; q++) { + LOG(a_sprintf("%d: %s\n", q, to_return[q])); + } +#endif + // now a special detour; fix the app name to remove quotes, which are + // not friendly to pass to exec. + if (app[0] == '"') app.zap(0, 0); + if (app[app.end()] == '"') app.zap(app.end(), app.end()); + return to_return; +} + +basis::un_int launch_process::run(const astring &app_name_in, const astring &command_line, + int flag, basis::un_int &child_id) +{ +#ifdef DEBUG_LAUNCH_PROCESS + FUNCDEF("run"); +#endif + child_id = 0; + astring app_name = app_name_in; + if (app_name[0] != '"') + app_name.insert(0, "\""); + if (app_name[app_name.end()] != '"') + app_name += "\""; +#ifdef __UNIX__ + // unix / linux implementation. + if (flag & RETURN_IMMEDIATELY) { + // they want to get back right away. + pid_t kid_pid = fork(); +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("launch fork returned %d\n", kid_pid)); +#endif + if (!kid_pid) { + // this is the child; we now need to launch into what we were asked for. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("process %d execing ", application_configuration::process_id()) + app_name + + " parms " + command_line + "\n"); +#endif + char_star_array parms = break_line(app_name, command_line); + execv(app_name.s(), parms.observe()); + // oops. failed to exec if we got to here. +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("child of fork (pid %d) failed to exec, error is ", + application_configuration::process_id()) + + critical_events::system_error_text(critical_events::system_error()) + + "\n"); +#endif + exit(0); // leave since this is a failed child process. + } else { + // this is the parent. let's see if the launch worked. + if (kid_pid == -1) { + // failure. + basis::un_int to_return = critical_events::system_error(); +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("parent %d is returning after failing to create, " + "error is ", application_configuration::process_id()) + + critical_events::system_error_text(to_return) + + "\n"); +#endif + return to_return; + } else { + // yes, launch worked okay. + child_id = kid_pid; + { + auto_synchronizer l(__process_synchronizer()); + __our_kids() += kid_pid; + } + // hook in our child exit signal handler. + signal(SIGCHLD, exiting_child_signal_handler); + +#ifdef DEBUG_LAUNCH_PROCESS + LOG(a_sprintf("parent %d is returning after successfully " + "creating %d ", application_configuration::process_id(), kid_pid) + app_name + + " parms " + command_line + "\n"); +#endif + return 0; + } + } + } else { + // assume they want to wait. + return system((app_name + " " + command_line).s()); + } +#elif defined(__WIN32__) + +//checking on whether we have admin rights for the launch. +//if (IsUserAnAdmin()) { +// MessageBox(0, (astring("IS admin with ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} else { +// MessageBox(0, (astring("NOT admin for ") + app_name_in + " " + command_line).s(), "launch process", MB_OK); +//} + + PROCESS_INFORMATION process_info; + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + +#ifdef SUPPORT_SHELL_EXECUTE + if (flag & SHELL_EXECUTE) { + // new code for using shell execute method--required on vista for proper + // launching with the right security tokens. + int show_cmd = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. + show_cmd = SW_HIDE; + } else { + show_cmd = SW_SHOWNORMAL; + } + + SHELLEXECUTEINFO exec_info; + ZeroMemory(&exec_info, sizeof(SHELLEXECUTEINFO)); + exec_info.cbSize = sizeof(SHELLEXECUTEINFO); + exec_info.fMask = SEE_MASK_NOCLOSEPROCESS // get the process info. + | SEE_MASK_FLAG_NO_UI; // turn off any visible error dialogs. + exec_info.hwnd = GetDesktopWindow(); +//hmmm: is get desktop window always appropriate? + to_unicode_persist(temp_verb, "open"); +//does "runas" work on xp also? or anywhere? + exec_info.lpVerb = temp_verb; + to_unicode_persist(temp_file, app_name); + exec_info.lpFile = temp_file; + to_unicode_persist(temp_parms, command_line); + exec_info.lpParameters = temp_parms; + exec_info.nShow = show_cmd; +// exec_info.hProcess = &process_info; + + BOOL worked = ShellExecuteEx(&exec_info); + if (!worked) + return critical_events::system_error(); + // copy out the returned process handle. + process_info.hProcess = exec_info.hProcess; + process_info.dwProcessId = GetProcessId(exec_info.hProcess); + } else { +#endif //shell exec + // standard windows implementation using CreateProcess. + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + int create_flag = 0; + if (flag & HIDE_APP_WINDOW) { + // magic that hides a console window for mswindows. +// version ver = portable::get_OS_version(); +// version vista_version(6, 0); +// if (ver < vista_version) { +// // we suspect that this flag is hosing us in vista. + create_flag = CREATE_NO_WINDOW; +// } + } + astring parms = app_name + " " + command_line; + bool success = CreateProcess(NIL, to_unicode_temp(parms), NIL, NIL, false, + create_flag, NIL, NIL, &startup_info, &process_info); + if (!success) + return critical_events::system_error(); + // success then, merge back into stream. + +#ifdef SUPPORT_SHELL_EXECUTE + } +#endif //shell exec + + // common handling for CreateProcess and ShellExecuteEx. + child_id = process_info.dwProcessId; + basis::un_long retval = 0; + if (flag & AWAIT_VIA_POLLING) { + // this type of waiting is done without blocking on the process. + while (true) { + MSG msg; + event_poll(msg); + // check if the process is gone yet. + BOOL ret = GetExitCodeProcess(process_info.hProcess, &retval); + if (!ret) { + break; + } else { + // if they aren't saying it's still active, then we will leave. + if (retval != STILL_ACTIVE) + break; + } + time_control::sleep_ms(14); + } + } else if (flag & AWAIT_APP_EXIT) { + // they want to wait for the process to exit. + WaitForInputIdle(process_info.hProcess, INFINITE); + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &retval); + } + // drop the process and thread handles. + if (process_info.hProcess) + CloseHandle(process_info.hProcess); + if (process_info.hThread) + CloseHandle(process_info.hThread); + return (basis::un_int)retval; +#else + #pragma error("hmmm: launch_process: no implementation for this OS.") +#endif + return 0; +} + +} // namespace. + diff --git a/nucleus/library/processes/launch_process.h b/nucleus/library/processes/launch_process.h new file mode 100644 index 00000000..a988c4c5 --- /dev/null +++ b/nucleus/library/processes/launch_process.h @@ -0,0 +1,102 @@ +#ifndef LAUNCH_PROCESS_CLASS +#define LAUNCH_PROCESS_CLASS + +/*****************************************************************************\ +* * +* Name : launch_process +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +// forward. +struct tagMSG; + +namespace processes { + +//! a simple wrapper of an array of char *, used by launch_process::break_line(). +class char_star_array : public basis::array +{ +public: + char_star_array() : basis::array(0, NIL, SIMPLE_COPY | EXPONE | FLUSH_INVISIBLE) {} + ~char_star_array() { + // clean up all the memory we're holding. + for (int i = 0; i < length(); i++) { + delete [] (use(i)); + } + } +}; + +////////////// + +//! Provides the capability to start processes in a variety of ways to run other applications. + +class launch_process : public virtual basis::nameable +{ +public: + DEFINE_CLASS_NAME("launch_process"); + + virtual ~launch_process() {} + + enum launch_flags { + HIDE_APP_WINDOW = 0x1, + //!< launches the application invisibly if possible. + AWAIT_APP_EXIT = 0x2, + //!< stays in the function until the launched application has exited. + RETURN_IMMEDIATELY = 0x4, + //!< starts the application and comes right back to the caller. + AWAIT_VIA_POLLING = 0x8, + //!< launches the app but polls and doesn't block on its exit. + SHELL_EXECUTE = 0x10 + //!< only valid on windows--uses ShellExecute instead of CreateProcess. + }; + + static basis::un_int run(const basis::astring &app_name, const basis::astring &command_line, + int flag, basis::un_int &child_id); + //!< starts an application using the "app_name" as the executable to run. + /*!< the "command_line" is the set of parameters to be passed to the app. + the return value is OS specific but can be identified using + system_error_text(). usually a zero return means success and non-zero + codes are errors. the "flag" is an XORed value from the process launch + flags that dictates how the app is to be started. in practice, only the + HIDE_APP_WINDOW flag can be combined with other values. if either AWAIT + flag is used, then the return value will be the launched process's own + exit value. the thread or process_id of the launched process is stored + in "child_id" if appropriate. */ + + static char_star_array break_line(basis::astring &app, const basis::astring ¶meters); + //!< prepares an "app" to launch with the "parameters" (via exec). + /*!< this breaks the strings for an application named "app" and its + "parameters" into an array of char * that is appropriate for the execv + function. */ + +private: +#ifdef __UNIX__ + static void exiting_child_signal_handler(int sig_num); + //!< awaits the child processes rather than leaving process handles willy nilly. +#endif +#ifdef __WIN32__ + static bool event_poll(tagMSG &message); + //!< tries to process one win32 event and retrieve the "message" from it. + /*!< this is a very general poll and will retrieve any message that's + available for the current thread. the message is actually processed + here also, by calling translate and dispatch. the returned structure + is mainly interesting for knowing what was done. */ +#endif + +}; + +} // namespace. + +#endif // outer guard. + diff --git a/nucleus/library/processes/letter.cpp b/nucleus/library/processes/letter.cpp new file mode 100644 index 00000000..d197aa19 --- /dev/null +++ b/nucleus/library/processes/letter.cpp @@ -0,0 +1,57 @@ +/*****************************************************************************\ +* * +* Name : letter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "letter.h" + +#include +#include +#include + +using namespace basis; +using namespace timely; + +namespace processes { + +letter::letter(int type, int start_after) +: _type(type), + _ready_time(new time_stamp(start_after)) +{} + +letter::letter(const letter &to_copy) +: _type(to_copy._type), + _ready_time(new time_stamp(*to_copy._ready_time)) +{ +} + +letter::~letter() +{ + _type = 0; + WHACK(_ready_time); +} + +bool letter::ready_to_send() { return time_stamp() >= *_ready_time; } + +void letter::set_ready_time(int start_after) +{ *_ready_time = time_stamp(start_after); } + +letter &letter::operator =(const letter &to_copy) +{ + if (this == &to_copy) return *this; + _type = to_copy._type; + *_ready_time = *to_copy._ready_time; + return *this; +} + +} //namespace. + diff --git a/nucleus/library/processes/letter.h b/nucleus/library/processes/letter.h new file mode 100644 index 00000000..50c77dfb --- /dev/null +++ b/nucleus/library/processes/letter.h @@ -0,0 +1,73 @@ +#ifndef LETTER_CLASS +#define LETTER_CLASS + +/*****************************************************************************\ +* * +* Name : letter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace processes { + +//! A virtual base class for pieces of "mail". Used by the mailbox object. + +class letter : public virtual basis::text_formable +{ +public: + letter(int type = 0, int start_after = 0); + //!< constructs a letter with the "type" and initial pause of "start_after". + /*!< a "type" for this letter must be specified, even if it is not intended + to be used. some letter managers may rely on this number identifying + different kinds of mail. the types should be unique within one mailbox. + a "type" of zero indicates an invalid letter. if the "start_after" is + non-zero, then it indicates that this letter should not be sent until + that many milliseconds have elapsed. */ + + letter(const letter &to_copy); + //!< copy constructor for base parts. + + virtual ~letter(); + //!< derived classes should also implement this. + /*!< a virtual destructor should be implemented by each derived class + to take care of class specific cleaning. note that the destructor should + NEVER attempt to use the mailbox system that it was stored in (or any + other mailbox system for that matter). this is necessary for prohibiting + deadlock conditions, but it's not that much of a restriction usually. */ + + letter &operator =(const letter &to_copy); + //!< assignment operator for base object. + + virtual void text_form(basis::base_string &fill) const = 0; + //!< derived letters must print a status blurb describing their contents. + + int type() const { return _type; } + //!< returns the type of letter held here. + + bool ready_to_send(); + //!< returns true if this letter is ready to + + void set_ready_time(int start_after); + //!< resets the time when this letter is ready to be sent. + /*!< the letter will now not be allowed to send until "start_after" + milliseconds from now. once the letter is added to a mailbox, it may + be too late to adjust this duration. */ + +private: + int _type; //!< the kind of mail this item represents. + timely::time_stamp *_ready_time; //!< time when this letter will be ready to send. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/mail_stop.h b/nucleus/library/processes/mail_stop.h new file mode 100644 index 00000000..12afe524 --- /dev/null +++ b/nucleus/library/processes/mail_stop.h @@ -0,0 +1,72 @@ +#ifndef MAIL_STOP_CLASS +#define MAIL_STOP_CLASS + +/*****************************************************************************\ +* * +* Name : mail_stop * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "safe_callback.h" + +#include + +namespace processes { + +// forward: +class letter; + +//! Base class for routes on which letters are automatically delivered. +/*! + The letters will show up for a particular unique id at this mail stop. + They are delivered by the object serving as the post office. +*/ + +class mail_stop : public safe_callback +{ +public: + + // it is required that the derived mail_stop invoke the end_availability() + // method in its destructor before it destroys any other objects. + + class items_to_deliver : public callback_data_block { + public: + items_to_deliver(const structures::unique_int &id, letter *package) + : _id(id), _package(package) {} + const structures::unique_int &_id; + letter *_package; + }; + + virtual void delivery_for_you(const structures::unique_int &id, letter *package) = 0; + //!< the derived object must provide this function. + /*!< prompts the recipient with the "id" to accept delivery of a "package". + the package can be modified as desired and MUST be recycled before + returning from this function. + IMPORTANT NOTE: the receiver MUST be thread-safe with respect to the + objects that it uses to handle this delivery! this is because mail is + delivered on a thread other than the main program thread. */ + +protected: + //! invoked by the safe callback machinery. + /*! this is implemented in this class and merely re-routes the call to the + more specific delivery_for_you() method. */ + virtual void real_callback(callback_data_block &data) { + items_to_deliver *bits = dynamic_cast(&data); + if (!bits) return; // bad type. + delivery_for_you(bits->_id, bits->_package); + } + +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/mailbox.cpp b/nucleus/library/processes/mailbox.cpp new file mode 100644 index 00000000..52b89adf --- /dev/null +++ b/nucleus/library/processes/mailbox.cpp @@ -0,0 +1,262 @@ +/*****************************************************************************\ +* * +* Name : mailbox * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "letter.h" +#include "mailbox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace loggers; +using namespace structures; +using namespace textual; + +namespace processes { + +const int MAILBOX_BITS = 9; + // we allow N bits in our table size, which means the table will have 2^N + // elements. careful with that increase... + +class mail_cabinet +{ +public: + amorph _waiting; + + mail_cabinet() : _waiting(0) {} + + ~mail_cabinet() { _waiting.reset(); } + + mail_cabinet(mail_cabinet &formal(to_copy)) { + non_continuable_error("mail_cabinet", "copy constructor", "should never be called"); + } + + mail_cabinet &operator =(mail_cabinet &formal(to_copy)) { + non_continuable_error("mail_cabinet", "assignment operator", + "should never be called"); + return *this; + } +}; + +////////////// + +class mailbox_bank : public int_hash +{ +public: + mailbox_bank() : int_hash (MAILBOX_BITS) {} + ~mailbox_bank() { reset(); } + + void get_ids(int_set &to_fill); + // returns the list of identifiers for people with mailboxes. + + void add_cabinet(const unique_int &id); + // creates a new mail receptacle for the "id". + + bool zap_cabinet(const unique_int &id); + // removes the cabinet for "id". + + void add_item(const unique_int &id, letter *to_add); + // stuffs an item "to_add" in for "id". + + bool get(const unique_int &id, letter * &to_receive); + // retrieves the next waiting package for "id" into "to_receive". + + void clean_up(); + // gets rid of any cabinets without any packages. +}; + +void mailbox_bank::clean_up() +{ + int_set ids; + get_ids(ids); + for (int i = 0; i < ids.elements(); i++) { + mail_cabinet *entry = find(ids[i]); + // if the cabinet has zero elements, we zap it. + if (!entry->_waiting.elements()) zap(ids[i]); + } +} + +void mailbox_bank::get_ids(int_set &to_fill) { to_fill = ids(); } + +void mailbox_bank::add_cabinet(const unique_int &id) +{ + if (find(id.raw_id())) return; // already exists. + mail_cabinet *to_add = new mail_cabinet; + add(id.raw_id(), to_add); +} + +bool mailbox_bank::zap_cabinet(const unique_int &id) +{ + if (!find(id.raw_id())) return false; // doesn't exist. + return zap(id.raw_id()); +} + +void mailbox_bank::add_item(const unique_int &id, letter *to_add) +{ + mail_cabinet *found = find(id.raw_id()); + if (!found) { + add_cabinet(id); + found = find(id.raw_id()); + // there should never be a failure case that would prevent the new cabinet + // from being added (besides overall memory failure). + if (!found) { +//complain + return; + } + } + found->_waiting.append(to_add); +} + +bool mailbox_bank::get(const unique_int &id, letter * &to_receive) +{ + mail_cabinet *found = find(id.raw_id()); + if (!found) return false; // no cabinet, much less any mail. + + if (!found->_waiting.elements()) return false; // no mail waiting. + for (int i = 0; i < found->_waiting.elements(); i++) { + // check if its time is ripe... + if (!found->_waiting.borrow(i)->ready_to_send()) continue; + // get the waiting mail and remove its old slot. + to_receive = found->_waiting.acquire(i); + found->_waiting.zap(i, i); + return true; + } + return false; +} + +////////////// + +mailbox::mailbox() +: _transaction_lock(new mutex), + _packages(new mailbox_bank) +{ +} + +mailbox::~mailbox() +{ + WHACK(_packages); + WHACK(_transaction_lock); +} + +void mailbox::get_ids(int_set &to_fill) +{ + auto_synchronizer l(*_transaction_lock); + _packages->get_ids(to_fill); +} + +void mailbox::drop_off(const unique_int &id, letter *package) +{ + auto_synchronizer l(*_transaction_lock); + _packages->add_item(id, package); +} + +void mailbox::clean_up() +{ + auto_synchronizer l(*_transaction_lock); + _packages->clean_up(); +} + +int mailbox::waiting(const unique_int &id) const +{ + auto_synchronizer l(*_transaction_lock); + mail_cabinet *found = _packages->find(id.raw_id()); + int to_return = 0; // if no cabinet, this is the proper count. + // if there is a cabinet, then get the size. + if (found) + to_return = found->_waiting.elements(); + return to_return; +} + +bool mailbox::pick_up(const unique_int &id, letter * &package) +{ + package = NIL; + auto_synchronizer l(*_transaction_lock); + return _packages->get(id, package); +} + +bool mailbox::close_out(const unique_int &id) +{ + auto_synchronizer l(*_transaction_lock); + bool ret = _packages->zap_cabinet(id); + return ret; +} + +void mailbox::show(astring &to_fill) +{ + auto_synchronizer l(*_transaction_lock); + int_set ids; + _packages->get_ids(ids); + for (int i = 0; i < ids.elements(); i++) { + mail_cabinet &mc = *_packages->find(ids[i]); + to_fill += astring(astring::SPRINTF, "cabinet %d:", ids[i]) + + parser_bits::platform_eol_to_chars(); + for (int j = 0; j < mc._waiting.elements(); j++) { + letter &l = *mc._waiting.borrow(j); + astring text; + l.text_form(text); + to_fill += string_manipulation::indentation(4) + + astring(astring::SPRINTF, "%4ld: ", j + 1) + + text + parser_bits::platform_eol_to_chars(); + } + } +} + +void mailbox::limit_boxes(int max_letters) +{ + auto_synchronizer l(*_transaction_lock); + int_set ids; + _packages->get_ids(ids); + for (int i = 0; i < ids.elements(); i++) { + mail_cabinet &mc = *_packages->find(ids[i]); + if (mc._waiting.elements() > max_letters) { + // this one needs cleaning. + mc._waiting.zap(max_letters, mc._waiting.elements() - 1); + } + } +} + +void mailbox::apply(apply_function *to_apply, void *data_link) +{ + auto_synchronizer l(*_transaction_lock); + int_set ids; + _packages->get_ids(ids); + for (int i = 0; i < ids.elements(); i++) { + mail_cabinet &mc = *_packages->find(ids[i]); + for (int j = 0; j < mc._waiting.elements(); j++) { + letter &l = *mc._waiting.borrow(j); + outcome ret = to_apply(l, ids[i], data_link); + if ( (ret == APPLY_WHACK) || (ret == APPLY_WHACK_STOP) ) { + // they wanted this node removed. + mc._waiting.zap(j, j); + j--; // skip back before missing guy so we don't omit anyone. + if (ret == APPLY_WHACK_STOP) + break; // they wanted to be done with it also. + } else if (ret == APPLY_STOP) { + break; // we hit the exit condition. + } + } + } +} + +} //namespace. + + diff --git a/nucleus/library/processes/mailbox.h b/nucleus/library/processes/mailbox.h new file mode 100644 index 00000000..e41fe946 --- /dev/null +++ b/nucleus/library/processes/mailbox.h @@ -0,0 +1,131 @@ +#ifndef MAILBOX_CLASS +#define MAILBOX_CLASS + +/*****************************************************************************\ +* * +* Name : mailbox * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace processes { + +class letter; +class mailbox_bank; + +//! Implements a thread safe "mail" delivery system. +/*! + Senders can drop packages off into the mailbox and the receivers can get + those packages back out of it. The base class for all mail items is also + provided in this library (letter.h). The name of this object is slightly + misleading; this object is really more of a post office. Each unique id + has its own mailbox slot for receiving mail. +*/ + +class mailbox : public virtual basis::root_object +{ +public: + mailbox(); + virtual ~mailbox(); + + void drop_off(const structures::unique_int &id, letter *package); + //!< drops a "package" in the mailbox for "id". + /*!< note that when you send a package to someone, you give up all + authority and control over that package. hopefully the end recipient + will eventually pick it up and then delete it. if the package is never + received, then this object will delete it. */ + + bool pick_up(const structures::unique_int &id, letter * &package); + //!< returns true if the mailbox for "id" had a "package" to be delivered. + /*!< don't forget to check multiple times on a true outcome, since there + could be more than one package waiting. false is returned when no more + mail is waiting. be careful; "package" could be a bomb. dynamic casts + seem appropriate as a method for ensuring that you get the type of + object you expect. note that once the invoker receives a package, it + is their responsibility to carefully manage it and then delete the + package after handling. not deleting the "package" pointer is grounds + for memory leaks. */ + + int waiting(const structures::unique_int &id) const; + //!< returns the number of items waiting for the "id" specified, if any. + + void get_ids(structures::int_set &to_fill); + //!< stuffs the set "to_fill" with the ids of all mailboxes present. + /*!< if you want only those mailboxes holding one or more letters, then + call the clean_up() method prior to this method. */ + + bool close_out(const structures::unique_int &id); + //!< dumps all packages stored for the "id" and shuts down its mailbox. + /*!< the destructors for those packages should never try to do anything + with the mailbox system or a deadlock could result. true is returned if + the "id" had a registered mailbox; false just indicates there was no box + to clear up. */ + + void show(basis::astring &to_fill); + //!< provides a picture of what's waiting in the mailbox. + /*!< this relies on the derived letter's required text_form() function. */ + + void clean_up(); + //!< removes any empty mailboxes from our list. + + void limit_boxes(int max_letters); + //!< establishes a limit on the number of letters. + /*!< this is a helper function for a very special mailbox; it has a + limited maximum size and any letters above the "max_letters" count will + be deleted. don't use this function on any mailbox where all letters + are important; your mailbox must have a notion of unreliability before + this would ever be appropriate. */ + + enum apply_outcomes { + OKAY = basis::common::OKAY, //!< continue apply process. + + DEFINE_OUTCOME(APPLY_STOP, -46, "Halt the apply process"), + DEFINE_OUTCOME(APPLY_WHACK, -47, "Removes the current letter, but " + "continues"), + DEFINE_OUTCOME(APPLY_WHACK_STOP, -48, "Halts apply and trashes the " + "current letter") + }; + + typedef basis::outcome apply_function(letter ¤t, int uid, void *data_link); + //!< the "apply_function" is what a user of apply() must provide. + /*!< the function will be called on every letter in the mailbox unless one + of the invocations returns APPLY_STOP or APPLY_WHACK_STOP; this causes + the apply process to stop (and zap the node for APPLY_WHACK). the "uid" + is the target for the "current" letter. the "data_link" provides a way + for the function to refer back to a parent class or data package of some + sort. note that all sorts of deadlocks will occur if your apply + function tries to do anything on the mailbox, even transitively. keep + those functions as simple as possible. */ + + void apply(apply_function *to_apply, void *data_link); + //!< calls the "to_apply" function on possibly every letter in the mailbox. + /*!< this iterates until the function returns a 'STOP' outcome. the + "data_link" pointer is passed to the apply function. NOTE: it is NOT safe + to rearrange or manipulate the mailbox in any way from your "to_apply" + function; the only changes allowed are those caused by the return value + from "to_apply". */ + +private: + basis::mutex *_transaction_lock; //!< keeps the state of the mailbox safe. + mailbox_bank *_packages; //!< the collection of mail that has arrived. + + // prohibited. + mailbox(const mailbox &); + mailbox &operator =(const mailbox &); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/makefile b/nucleus/library/processes/makefile new file mode 100644 index 00000000..57e23f6e --- /dev/null +++ b/nucleus/library/processes/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = processes +TYPE = library +TARGETS = processes.lib +SOURCE = configured_applications.cpp ethread.cpp heartbeat.cpp launch_process.cpp \ + letter.cpp mailbox.cpp post_office.cpp \ + process_control.cpp process_entry.cpp rendezvous.cpp safe_callback.cpp safe_roller.cpp \ + state_machine.cpp thread_cabinet.cpp + +include cpp/rules.def + diff --git a/nucleus/library/processes/os_event.h b/nucleus/library/processes/os_event.h new file mode 100644 index 00000000..9e52c388 --- /dev/null +++ b/nucleus/library/processes/os_event.h @@ -0,0 +1,53 @@ +#ifndef OS_EVENT_CLASS +#define OS_EVENT_CLASS + +/*****************************************************************************\ +* * +* Name : OS_event * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "letter.h" + +#include +#include + +namespace processes { + +// forward. +class post_office; + +//! Models an OS-level event so we can represent activities occurring there. + +class OS_event : public letter, public virtual basis::text_formable +{ +public: + basis::un_int _message; + basis::un_int _parm1; + basis::un_int _parm2; + + DEFINE_CLASS_NAME("OS_event"); + + OS_event(int event_type, basis::un_int message, basis::un_int parm1, basis::un_int parm2) + : letter(event_type), _message(message), _parm1(parm1), _parm2(parm2) {} + + virtual void text_form(basis::base_string &fill) const { + fill.assign(text_form()); + } + basis::astring text_form() const { + return basis::a_sprintf("os_event: msg=%d parm1=%d parm2=%d", _message, _parm1, _parm2); + } +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/post_office.cpp b/nucleus/library/processes/post_office.cpp new file mode 100644 index 00000000..523e4bb3 --- /dev/null +++ b/nucleus/library/processes/post_office.cpp @@ -0,0 +1,391 @@ +/*****************************************************************************\ +* * +* Name : post_office * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ethread.h" +#include "letter.h" +#include "mailbox.h" +#include "post_office.h" +#include "thread_cabinet.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; + +namespace processes { + +//#define DEBUG_POST_OFFICE + // uncomment if you want the noisy version. + +#undef LOG +#define LOG(a) CLASS_EMERGENCY_LOG(program_wide_logger::get(), a) + +const int CLEANING_INTERVAL = 14 * SECOND_ms; + // the interval between cleaning of extra letters and dead mailboxes. + +const int SNOOZE_TIME_FOR_POSTMAN = 42; + // we'll snooze for this long if absolutely nothing happened during the + // thread's activation. if things are going on, our snooze time is reduced + // by the length of time we were delivering items. + +const int DELIVERIES_ALLOWED = 350; + // the maximum number of deliveries we'll try to get done per thread run. + +////////////// + +//hmmm: arrhhh--maybe we need to spawn a thread per postal route. + +class postal_carrier : public ethread +{ +public: + postal_carrier(post_office &parent, const unique_int &route) + : ethread(SNOOZE_TIME_FOR_POSTMAN, ethread::SLACK_INTERVAL), + _parent(parent), + _route(route) + {} + + DEFINE_CLASS_NAME("postal_carrier"); + + void perform_activity(void *) { + FUNCDEF("perform_activity"); + bool finished; + try { + finished = _parent.deliver_mail_on_route(_route, *this); + } catch(...) { + LOG(astring("caught exception during mail delivery!")); + } + if (!finished) { + // not finished delivering all items. + reschedule(); + } else { + reschedule(SNOOZE_TIME_FOR_POSTMAN); + } + } + +private: + post_office &_parent; + unique_int _route; +}; + +////////////// + +class postal_cache : public mailbox {}; + +////////////// + +class tagged_mail_stop : public virtual text_formable +{ +public: + mail_stop *_route; + unique_int _thread_id; + unique_int _id; + + tagged_mail_stop(const unique_int &id = 0, mail_stop *route = NIL, + const unique_int &thread_id = 0) + : _route(route), _thread_id(thread_id), _id(id) {} + + DEFINE_CLASS_NAME("tagged_mail_stop"); + + virtual void text_form(basis::base_string &fill) const { + fill.assign(text_form()); + } + + virtual astring text_form() const { + return a_sprintf("%s: id=%d, addr=%08lx, thr_id=%d", + static_class_name(), _id.raw_id(), _route, _thread_id.raw_id()); + } +}; + +////////////// + +class route_map : public amorph +{ +public: + tagged_mail_stop *find(const unique_int &id) { + for (int i = 0; i < elements(); i++) { + tagged_mail_stop *curr = borrow(i); + if (curr && (curr->_id == id)) return curr; + } + return NIL; + } + + bool zap(const unique_int &id) { + for (int i = 0; i < elements(); i++) { + tagged_mail_stop *curr = borrow(i); + if (curr && (curr->_id == id)) { + amorph::zap(i, i); + return true; + } + } + return false; + } + +}; + +////////////// + +class letter_morph : public amorph {}; + +////////////// + +post_office::post_office() +: _post(new mailbox), + _routes(new route_map), + _next_cleaning(new time_stamp), + _threads(new thread_cabinet) +{ +} + +post_office::~post_office() +{ + stop_serving(); + WHACK(_post); + WHACK(_routes); + WHACK(_next_cleaning); + WHACK(_threads); +} + +void post_office::show_routes(astring &to_fill) +{ + auto_synchronizer l(c_mutt); +//hmmm: simplify this; just use the int_set returning func and print that. + astring current_line; + astring temp; + if (_routes->elements()) + to_fill += astring("Mail Delivery Routes:") + parser_bits::platform_eol_to_chars(); + + for (int i = 0; i < _routes->elements(); i++) { + const tagged_mail_stop *tag = _routes->get(i); + if (!tag) continue; + temp = astring(astring::SPRINTF, "%d ", tag->_id.raw_id()); + if (current_line.length() + temp.length() >= 80) { + current_line += parser_bits::platform_eol_to_chars(); + to_fill += current_line; + current_line.reset(); + } + current_line += temp; + } + // catch the last line we created. + if (!!current_line) to_fill += current_line; +} + +void post_office::stop_serving() { if (_threads) _threads->stop_all(); } + +void post_office::show_mail(astring &output) +{ + output.reset(); + output += parser_bits::platform_eol_to_chars(); + output += astring("Mailbox Contents at ") + time_stamp::notarize(true) + + parser_bits::platform_eol_to_chars() + parser_bits::platform_eol_to_chars(); + astring box_state; + _post->show(box_state); + if (box_state.t()) output += box_state; + else + output += astring("No items are awaiting delivery.") + + parser_bits::platform_eol_to_chars(); +} + +void post_office::drop_off(const unique_int &id, letter *package) +{ +#ifdef DEBUG_POST_OFFICE + FUNCDEF("drop_off"); + LOG(astring(astring::SPRINTF, "mailbox drop for %d: ", id) + + package->text_form()); +#endif + _post->drop_off(id, package); +#ifdef DEBUG_POST_OFFICE + if (!route_listed(id)) { + LOG(a_sprintf("letter for %d has no route!", id)); + } +#endif +} + +bool post_office::pick_up(const unique_int &id, letter * &package) +{ +#ifdef DEBUG_POST_OFFICE + FUNCDEF("pick_up"); +#endif + bool to_return = _post->pick_up(id, package); +#ifdef DEBUG_POST_OFFICE + if (to_return) + LOG(astring(astring::SPRINTF, "mailbox grab for %d: ", id) + + package->text_form()); +#endif + return to_return; +} + +bool post_office::route_listed(const unique_int &id) +{ + int_set route_set; + get_route_list(route_set); + return route_set.member(id.raw_id()); +} + +void post_office::get_route_list(int_set &route_set) +{ + auto_synchronizer l(c_mutt); + + // gather the set of routes that we should carry mail to. + route_set.reset(); + + if (!_routes->elements()) { + // if there are no elements, why bother iterating? + return; + } + + for (int i = 0; i < _routes->elements(); i++) { + const tagged_mail_stop *tag = _routes->get(i); + if (!tag) continue; + route_set.add(tag->_id.raw_id()); + } +} + +void post_office::clean_package_list(post_office &formal(post), + letter_morph &to_clean) +{ + FUNCDEF("clean_package_list"); + auto_synchronizer l(c_mutt); + + // recycle all the stuff we had in the list. + while (to_clean.elements()) { + letter *package = to_clean.acquire(0); + to_clean.zap(0, 0); + if (!package) { + LOG("saw empty package in list to clean!"); + continue; + } + WHACK(package); + } +} + +bool post_office::deliver_mail_on_route(const unique_int &route, + ethread &carrier) +{ + FUNCDEF("deliver_mail_on_route"); + auto_synchronizer l(c_mutt); + +#ifdef DEBUG_POST_OFFICE + time_stamp enter; +#endif + if (carrier.should_stop()) return true; // get out if thread was told to. + + int deliveries = 0; // number of items delivered so far. + letter_morph items_for_route; + // holds the items that need to be sent to this route. + + // pickup all of the mail that we can for this route. + while (deliveries < DELIVERIES_ALLOWED) { + if (carrier.should_stop()) + return true; // get out if thread was told to. + letter *package; + if (!_post->pick_up(route, package)) { + // there are no more letters for this route. + break; // skip out of the loop. + } + deliveries++; // count this item as a delivery. + items_for_route.append(package); + } + + if (!items_for_route.elements()) return true; // nothing to handle. + + // locate the destination for this route. + tagged_mail_stop *real_route = _routes->find(route); // find the route. + if (!real_route) { + // we failed to find the route we wanted... + LOG(astring(astring::SPRINTF, "route %d disappeared!", route.raw_id())); + clean_package_list(*this, items_for_route); + return true; + } + + // now deliver what we found for this route. + for (int t = 0; t < items_for_route.elements(); t++) { + if (carrier.should_stop()) { + // get out if thread was told to. + return true; + } + letter *package = items_for_route.acquire(t); + // hand the package out on the route. + mail_stop::items_to_deliver pack(route, package); + real_route->_route->invoke_callback(pack); + // the callee is responsible for cleaning up. + } + + bool finished_all = (deliveries < DELIVERIES_ALLOWED); + // true if we handled everything we could have. + + if (carrier.should_stop()) return true; // get out if thread was told to. + + // this bit is for the post office at large, but we don't want an extra + // thread when we've got all these others handy. + bool cleaning_time = time_stamp() > *_next_cleaning; + if (cleaning_time) { + _post->clean_up(); // get rid of dead mailboxes in main post office. + _next_cleaning->reset(CLEANING_INTERVAL); + } + + time_stamp exit; +#ifdef DEBUG_POST_OFFICE + int duration = int(exit.value() - enter.value()); + if (duration > 20) + LOG(a_sprintf("deliveries took %d ms.", duration)); +#endif + return finished_all; +} + +bool post_office::register_route(const unique_int &id, + mail_stop &carrier_path) +{ + auto_synchronizer l(c_mutt); + + tagged_mail_stop *found = _routes->find(id); + if (found) return false; // already exists. + + postal_carrier *po = new postal_carrier(*this, id); + unique_int thread_id = _threads->add_thread(po, false, NIL); + // add the thread so we can record its id. + tagged_mail_stop *new_stop = new tagged_mail_stop(id, &carrier_path, + thread_id); + _routes->append(new_stop); + // add the mail stop to our listings. + po->start(NIL); + // now start the thread so it can begin cranking. + return true; +} + +bool post_office::unregister_route(const unique_int &id) +{ + auto_synchronizer l(c_mutt); + + tagged_mail_stop *tag = _routes->find(id); + if (!tag) return false; // doesn't exist yet. + unique_int thread_id = tag->_id; + _routes->zap(id); + _threads->zap_thread(thread_id); + return true; +} + +} //namespace. + + diff --git a/nucleus/library/processes/post_office.h b/nucleus/library/processes/post_office.h new file mode 100644 index 00000000..da53e6a1 --- /dev/null +++ b/nucleus/library/processes/post_office.h @@ -0,0 +1,115 @@ +#ifndef CENTRAL_MAILBOX_CLASS +#define CENTRAL_MAILBOX_CLASS + +/*****************************************************************************\ +* * +* Name : post_office * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "mail_stop.h" + +#include +#include + +namespace processes { + +class letter; +class letter_morph; +class mailbox; +class postal_cache; +class route_map; +class thread_cabinet; + +//! Manages a collection of mailboxes and implements delivery routes for mail. + +class post_office +{ +public: + post_office(); + + virtual ~post_office(); + //!< stop_serving must be invoked prior to this destructor. + + void stop_serving(); + //!< gets the mailbox to stop delivering items prior to a shutdown. + + // informational functions... + + DEFINE_CLASS_NAME("post_office"); + + void show_mail(basis::astring &to_fill); + //!< prints a snapshot of all currently pending letters into "to_fill". + + void show_routes(basis::astring &to_fill); + //!< writes a listing of the current routes into "to_fill". + + // general delivery services subject to the constraints of the mailbox class. + + void drop_off(const structures::unique_int &id, letter *package); + //!< sends a "package" on its way to the "id" via the registered route. + /*!< note that mail is not rejected if there is no known route to the + mail_stop for the "id"; it is assumed in that case that the recipient + will check at the post office. */ + + bool pick_up(const structures::unique_int &id, letter * &package); + //!< retrieves a "package" intended for the "id" if one exists. + /*!< false is returned if none are available. on success, the "package" is + filled in with the address of the package and it is the caller's + responsibility to destroy or recycle() it after dealing with it. */ + + ////////////// + + // mail delivery and routing support. + + bool register_route(const structures::unique_int &id, mail_stop &carrier_path); + //!< registers a route "carrier_path" for mail deliveries to the "id". + + bool unregister_route(const structures::unique_int &id); + //!< removes a route for the "id". + /*!< this should be done before the object's destructor is invoked since + the letter carrier could be on his way with a letter at an arbitrary time. + also, the mail_stop should be shut down (with end_availability()) at that + time also. if those steps are taken, then the carrier is guaranteed not + to bother the recipient. */ + + bool route_listed(const structures::unique_int &id); + //!< returns true if there is a route listed for the "id". + /*!< this could change at any moment, since another place in the source + code could remove the route just after this call. it is information from + the past by the time it's returned. */ + + ////////////// + + bool deliver_mail_on_route(const structures::unique_int &route, ethread &carrier); + //!< for internal use only--delivers the letters to known routes. + /*!< this function should only be used internally to prompt the delivery + of packages that are waiting for objects we have a route to. it returns + true when all items that were waiting have been sent. */ + +private: + basis::mutex c_mutt; //!< protects our lists and dead letter office from corruption. + mailbox *_post; //!< the items awaiting handling. + route_map *_routes; //!< the pathways that have been defined. + timely::time_stamp *_next_cleaning; //!< when the next mailbox flush will occur. + thread_cabinet *_threads; //!< our list of threads for postal routes. + + void get_route_list(structures::int_set &route_set); + //!< retrieves the list of routes that we have registered. + + void clean_package_list(post_office &post, letter_morph &to_clean); + //!< recycles all of the letters held in "to_clean". +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/process_control.cpp b/nucleus/library/processes/process_control.cpp new file mode 100644 index 00000000..ce0dbf03 --- /dev/null +++ b/nucleus/library/processes/process_control.cpp @@ -0,0 +1,606 @@ + +//NOTE: +// +// this thing is showing bad behavior on win32 when unicode is enabled. +// therefore unicode is currently disabled for win32, which is a shame. +// but something needs to be fixed in our unicode conversion stuff; the unicode versions +// of the file names were not getting correctly back-converted into the ascii counterpart, +// and *that* is broken. +// ** this may be a widespread issue in the win32 code related to unicode right now! +// ** needs further investigation when a reason to better support ms-windows emerges. +// +// => but don't panic... right now things work as long as you do a utf-8 / ascii build, +// which is how everything's configured. + + +/* +* Name : process_control +* Author : Chris Koeritz +** +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "process_entry.h" +#include "process_control.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; + +namespace processes { + +#ifdef __WIN32__ + #include + const astring NTVDM_NAME = "ntvdm.exe"; + // the umbrella process that hangs onto 16 bit tasks for NT. + #ifdef _MSCVER + #include + #endif +#endif +#ifdef __UNIX__ + #include + #include +#endif + +//#define DEBUG_PROCESS_CONTROL + // uncomment for noisier debugging. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +////////////// + +class process_implementation_hider +{ +public: +#ifdef __WIN32__ + // psapi members: + application_instance psapi_dll; + application_instance vdm_dll; + BOOL (WINAPI *enumerate_processes)(basis::un_int *, basis::un_int cb, basis::un_int *); + BOOL (WINAPI *enumerate_modules)(HANDLE, HMODULE *, basis::un_int, basis::un_int *); + basis::un_int (WINAPI *get_module_name)(HANDLE, HMODULE, LPTSTR, basis::un_int); +#ifdef _MSCVER + INT (WINAPI *tasker_16bit)(basis::un_int, TASKENUMPROCEX fp, LPARAM); +#endif + + // toolhelp members: + application_instance kernel32_dll; + HANDLE (WINAPI *create_snapshot)(basis::un_int,basis::un_int); + BOOL (WINAPI *first_process)(HANDLE,LPPROCESSENTRY32); + BOOL (WINAPI *next_process)(HANDLE,LPPROCESSENTRY32); + + // get an atomic view of the process list, which rapidly becomes out of date. +/// HANDLE hSnapShot; + + process_implementation_hider() + : psapi_dll(NIL), vdm_dll(NIL), enumerate_processes(NIL), + enumerate_modules(NIL), get_module_name(NIL), +#ifdef _MSCVER + tasker_16bit(NIL), +#endif + kernel32_dll(NIL), create_snapshot(NIL), first_process(NIL), + next_process(NIL) {} + + ~process_implementation_hider() { + if (psapi_dll) FreeLibrary(psapi_dll); + if (vdm_dll) FreeLibrary(vdm_dll); + if (kernel32_dll) FreeLibrary(kernel32_dll); + psapi_dll = NIL; + vdm_dll = NIL; + kernel32_dll = NIL; + } +#endif +}; + +////////////// + +class process_info_clump +{ +public: + basis::un_int _process_id; + process_entry_array &_to_fill; // where to add entries. + + process_info_clump(basis::un_int id, process_entry_array &to_fill) + : _process_id(id), _to_fill(to_fill) {} +}; + +////////////// + +// top-level functions... + +process_control::process_control() +: _ptrs(new process_implementation_hider), +#ifdef __WIN32__ + _use_psapi(true), +#endif +#ifdef __UNIX__ + _rando(new chaos), +#endif + _healthy(false) +{ + // Check to see if were running under Windows95 or Windows NT. + version osver = application_configuration::get_OS_version(); + +#ifdef __WIN32__ + if (osver.v_revision() == VER_PLATFORM_WIN32_WINDOWS) { + // we're on Windows 95, so use the toolhelp API for the processes. + _use_psapi = false; + } else if (osver.v_major() >= 5) { + // w2k and onward can do the toolhelp instead of psapi. + _use_psapi = false; + } + if (_use_psapi) + _healthy = initialize_psapi_support(); + else + _healthy = initialize_toolhelp_support(); +#endif +#ifdef __UNIX__ + _healthy = true; +#endif +} + +process_control::~process_control() +{ + WHACK(_ptrs); +#ifdef __UNIX__ + WHACK(_rando); +#endif +} + +void process_control::sort_by_name(process_entry_array &v) +{ + process_entry temp; + for (int gap = v.length() / 2; gap > 0; gap /= 2) + for (int i = gap; i < v.length(); i++) + for (int j = i - gap; j >= 0 + && (filename(v[j].path()).basename().raw() + > filename(v[j + gap].path()).basename().raw()); + j = j - gap) + { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; } +} + +void process_control::sort_by_pid(process_entry_array &v) +{ + process_entry temp; + for (int gap = v.length() / 2; gap > 0; gap /= 2) + for (int i = gap; i < v.length(); i++) + for (int j = i - gap; j >= 0 && (v[j]._process_id + > v[j + gap]._process_id); j = j - gap) + { temp = v[j]; v[j] = v[j + gap]; v[j + gap] = temp; } +} + +bool process_control::query_processes(process_entry_array &to_fill) +{ + if (!_healthy) return false; +#ifdef __WIN32__ + if (!_use_psapi) { + // we're on Windows 95 or something, so use the toolhelp API for the + // processes. + return get_processes_with_toolhelp(to_fill); + } else { + // we're on Windows NT and so on; use the process API (PSAPI) to get info. + return get_processes_with_psapi(to_fill); + } +#endif +#ifdef __UNIX__ + return get_processes_with_ps(to_fill); +#endif +} + +#ifdef __WIN32__ +bool process_control::initialize_psapi_support() +{ + // create an instance of the PSAPI dll for querying 32-bit processes and + // an instance of the VDM dll support just in case there are also some + // 16-bit processes. + _ptrs->psapi_dll = LoadLibraryA("psapi.dll"); + if (!_ptrs->psapi_dll) return false; + _ptrs->vdm_dll = LoadLibraryA("vdmdbg.dll"); + if (!_ptrs->vdm_dll) return false; + + // locate the functions that we want to call. + _ptrs->enumerate_processes = (BOOL(WINAPI *)(basis::un_int *,basis::un_int,basis::un_int*)) + GetProcAddress(_ptrs->psapi_dll, "EnumProcesses"); + _ptrs->enumerate_modules + = (BOOL(WINAPI *)(HANDLE, HMODULE *, basis::un_int, basis::un_int *)) + GetProcAddress(_ptrs->psapi_dll, "EnumProcessModules"); + _ptrs->get_module_name + = (basis::un_int (WINAPI *)(HANDLE, HMODULE, LPTSTR, basis::un_int)) + GetProcAddress(_ptrs->psapi_dll, "GetModuleFileNameExA"); +#ifdef _MSCVER + _ptrs->tasker_16bit = (INT(WINAPI *)(basis::un_int, TASKENUMPROCEX, LPARAM)) + GetProcAddress(_ptrs->vdm_dll, "VDMEnumTaskWOWEx"); +#endif + if (!_ptrs->enumerate_processes || !_ptrs->enumerate_modules + || !_ptrs->get_module_name +#ifdef _MSCVER + || !_ptrs->tasker_16bit +#endif + ) return false; + + return true; +} + +bool process_control::initialize_toolhelp_support() +{ + // get hooked up with the kernel dll so we can use toolhelp functions. + _ptrs->kernel32_dll = LoadLibraryA("Kernel32.DLL"); + if (!_ptrs->kernel32_dll) return false; + + // create pointers to the functions we want to invoke. + _ptrs->create_snapshot = (HANDLE(WINAPI *)(basis::un_int,basis::un_int)) + GetProcAddress(_ptrs->kernel32_dll, "CreateToolhelp32Snapshot"); + _ptrs->first_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32)) + GetProcAddress(_ptrs->kernel32_dll, "Process32First"); + _ptrs->next_process = (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32)) + GetProcAddress(_ptrs->kernel32_dll, "Process32Next"); + if (!_ptrs->next_process || !_ptrs->first_process + || !_ptrs->create_snapshot) return false; + return true; +} + +#endif + +bool process_control::zap_process(basis::un_int to_zap) +{ +#ifdef DEBUG_PROCESS_CONTROL + FUNCDEF("zap_process"); +#endif + if (!_healthy) return false; +#ifdef __UNIX__ + int ret = kill(to_zap, 9); + // send the serious take-down signal to the process. + return !ret; +#endif +#ifdef __WIN32__ + HANDLE h = OpenProcess(PROCESS_TERMINATE, false, to_zap); + if (!h) { +#ifdef DEBUG_PROCESS_CONTROL + int err = critical_events::system_error(); + LOG(a_sprintf("error zapping process %d=", to_zap) + + critical_events::system_error_text(err)); +#endif + return false; + } + int exit_code = 0; + BOOL ret = TerminateProcess(h, exit_code); + CloseHandle(h); + return !!ret; +#endif +} + +process_entry process_control::query_process(basis::un_int to_query) +{ +// FUNCDEF("query_process"); + process_entry to_return; + + process_entry_array to_fill; + bool got_em = query_processes(to_fill); + if (!got_em) return to_return; + + for (int i = 0; i < to_fill.length(); i++) { + if (to_fill[i]._process_id == to_query) + return to_fill[i]; + } + +//hmmm: implement more specifically. +#ifdef __UNIX__ +//put in the single process grabber deal. +#endif +#ifdef __WIN32__ +//grab the entry from the list. +#endif + + return to_return; +} + +bool process_control::find_process_in_list(const process_entry_array &processes, + const astring &app_name_in, int_set &pids) +{ +#ifdef DEBUG_PROCESS_CONTROL + FUNCDEF("find_process_in_list"); +#endif + pids.clear(); + astring app_name = app_name_in.lower(); + + version os_ver = application_configuration::get_OS_version(); + + bool compare_prefix = (os_ver.v_major() == 5) && (os_ver.v_minor() == 0); + // we only compare the first 15 letters due to a recently noticed bizarre + // bug where w2k only shows (and reports) the first 15 letters of file + // names through toolhelp. + + bool found = false; // was it seen in process list? + for (int i = 0; i < processes.length(); i++) { + filename path = processes[i].path(); + astring base = path.basename().raw().lower(); + // a kludge for w2k is needed--otherwise we will miss seeing names that + // really are running due to the toolhelp api being busted and only + // reporting the first 15 characters of the name. + if ( (compare_prefix && (base.compare(app_name, 0, 0, 15, false))) + || (base == app_name) ) { + found = true; + pids.add(processes[i]._process_id); + } + } +#ifdef DEBUG_PROCESS_CONTROL + if (!found) + LOG(astring("failed to find the program called ") + app_name); +#endif + return found; +} + +////////////// + +#ifdef __WIN32__ +// this section is the PSAPI version of the query. + +// called back on each 16 bit task. +BOOL WINAPI process_16bit(basis::un_int dwThreadId, WORD module_handle16, WORD hTask16, + PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined) +{ + process_info_clump *to_stuff = (process_info_clump *)lpUserDefined; + process_entry to_add; + to_add._process_id = to_stuff->_process_id; + to_add._module16 = hTask16; + to_add.path(pszFileName); +//threads, etc? + to_stuff->_to_fill += to_add; + return true; +} + +bool process_control::get_processes_with_psapi(process_entry_array &to_fill) +{ + // prepare the result object. + to_fill.reset(); + + // loop over the process enumeration function until we are sure that we + // have allocated a large enough space for all existing processes. + bool got_all = false; + basis::un_int *pid_list = NIL; + basis::un_int max_size = 428 * sizeof(basis::un_int); + basis::un_int actual_size = 0; + while (!got_all) { + pid_list = (basis::un_int *)HeapAlloc(GetProcessHeap(), 0, max_size); + if (!pid_list) return false; + if (!_ptrs->enumerate_processes(pid_list, max_size, &actual_size)) { + HeapFree(GetProcessHeap(), 0, pid_list); + return false; + } + if (actual_size == max_size) { + // there were too many to store, so whack the partial list. + HeapFree(GetProcessHeap(), 0, pid_list); + max_size *= 2; // try with twice as much space. + } else got_all = true; + } + + // calculate the number of process ids that got stored. + basis::un_int ids = actual_size / sizeof(basis::un_int); + + // examine each process id that we found. + for (basis::un_int i = 0; i < ids; i++) { + // get process information if security permits. +//turn chunk below into "scan process" or something. + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + false, pid_list[i]); + flexichar process_name[MAX_ABS_PATH + 1] = { '\0' }; + if (hProcess) { + // go over the modules for the process. the first will be the main + // application module and the others will be threads. ??? + basis::un_int max_size = 1 * sizeof(HMODULE); +//hmmm: could do a rescan loop here if too many. + basis::un_int actual_size = 0; + HMODULE *module_handles = new HMODULE[max_size + 1]; + if (!module_handles) { + CloseHandle(hProcess); + HeapFree(GetProcessHeap(), 0, pid_list); + return false; + } + if (_ptrs->enumerate_modules(hProcess, module_handles, max_size, + &actual_size)) { + // we want the name of the first module. + if (!_ptrs->get_module_name(hProcess, *module_handles, process_name, + sizeof(process_name))) + process_name[0] = 0; + } + WHACK(module_handles); + CloseHandle(hProcess); + } + + // we add whatever information we were able to find about this process. + process_entry new_entry; + new_entry._process_id = pid_list[i]; + astring converted_name = from_unicode_temp(process_name); + new_entry.path(converted_name); + +//how to get? performance data helper? +/// new_entry._threads = threads; + to_fill += new_entry; + + // if we're looking at ntvdm, then there might be 16 bit processes + // attached to it. + if (new_entry.path().length() >= NTVDM_NAME.length()) { + astring temp = new_entry.path().substring + (new_entry.path().end() - NTVDM_NAME.length() + 1, + new_entry.path().end()); + temp.to_lower(); +#ifdef _MSCVER +//hmmm: pull this back in for mingw when it seems to be supported, if ever. + if (temp == NTVDM_NAME) { + // set up a callback stampede on the 16 bit processes. + process_info_clump info(pid_list[i], to_fill); + _ptrs->tasker_16bit(pid_list[i], (TASKENUMPROCEX)process_16bit, + (LPARAM)&info); + } +#endif + } + } + + if (pid_list) HeapFree(GetProcessHeap(), 0, pid_list); + return true; +} + +////////////// + +// this is the toolhelp version of the query. + +bool process_control::get_processes_with_toolhelp(process_entry_array &to_fill) +{ + // prepare the result object. + to_fill.reset(); + + // get an atomic view of the process list, which rapidly becomes out of date. + HANDLE hSnapShot; + hSnapShot = _ptrs->create_snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapShot == INVALID_HANDLE_VALUE) return false; + + // start iterating through the snapshot by getting the first process. + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + BOOL keep_going = _ptrs->first_process(hSnapShot, &entry); + + // while we see valid processes, iterate through them. + while (keep_going) { + // add an entry for the current process. + process_entry new_entry; + new_entry._process_id = entry.th32ProcessID; + new_entry._references = entry.cntUsage; + new_entry._threads = entry.cntThreads; + new_entry._parent_process_id = entry.th32ParentProcessID; + astring exe_file = from_unicode_temp(entry.szExeFile); + new_entry.path(exe_file); + to_fill += new_entry; + entry.dwSize = sizeof(PROCESSENTRY32); // reset struct size. + keep_going = _ptrs->next_process(hSnapShot, &entry); + } + + CloseHandle(hSnapShot); + return true; +} +#endif // __WIN32__ + +#ifdef __UNIX__ + +#define CLOSE_TMP_FILE { \ +/* continuable_error("process_control", "get_processes_with_ps", error); */ \ + if (output) { \ + fclose(output); \ + unlink(tmpfile.s()); \ + } \ +} + +bool process_control::get_processes_with_ps(process_entry_array &to_fill) +{ + FUNCDEF("get_processes_with_ps"); + to_fill.reset(); + // we ask the operating system to give us a list of processes. + a_sprintf tmpfile("/tmp/proc_list_%d_%d.txt", application_configuration::process_id(), + _rando->inclusive(1, 400000)); + a_sprintf cmd("ps wax --format \"%%p %%a\" >%s", tmpfile.s()); +//hmmm: add more info as we expand the process entry. + FILE *output = NIL; // initialize now to establish variable for our macro. + int sysret = system(cmd.s()); + if (negative(sysret)) { +LOG("got negative return from system()!"); + CLOSE_TMP_FILE; + return false; + } + output = fopen(tmpfile.s(), "r"); + if (!output) { +LOG("failed to open process list file!"); + CLOSE_TMP_FILE; + return false; + } + const int max_buff = 10000; + char buff[max_buff]; + size_t size_read = 1; + astring accumulator; + while (size_read > 0) { + // read bytes from the file. + size_read = fread(buff, 1, max_buff, output); + // if there was anything, store it in the string. + if (size_read > 0) + accumulator += astring(astring::UNTERMINATED, buff, size_read); + } + CLOSE_TMP_FILE; + // parse the string up now. + bool first_line = true; + while (accumulator.length()) { + // eat any spaces in front. + if (first_line) { + // we toss the first line since it's a header with columns. + int cr_indy = accumulator.find('\n'); + accumulator.zap(0, cr_indy); + if (accumulator[accumulator.end()] == '\r') + accumulator.zap(accumulator.end(), accumulator.end()); + first_line = false; + continue; + } + while (accumulator.length() && (accumulator[0] == ' ')) + accumulator.zap(0, 0); + // look for the first part of the line; the process id. + int num_indy = accumulator.find(' '); + if (negative(num_indy)) break; + basis::un_int p_id = accumulator.substring(0, num_indy).convert(0); + accumulator.zap(0, num_indy); + int cr_indy = accumulator.find('\n'); + if (negative(cr_indy)) + cr_indy = accumulator.end() + 1; + astring proc_name = accumulator.substring(0, cr_indy - 1); + if (proc_name[proc_name.end()] == '\r') + proc_name.zap(proc_name.end(), proc_name.end()); + accumulator.zap(0, cr_indy); + int space_indy = proc_name.find(' '); +//hmmm: this is incorrect regarding names that do have spaces in them. + if (negative(space_indy)) + space_indy = proc_name.end() + 1; + process_entry to_add; + to_add._process_id = p_id; + astring path = proc_name.substring(0, space_indy - 1); + // patch the pathname if we see any bracketed items. + int brackets_in = 0; + for (int i = 0; i < path.length(); i++) { + if (path[i] == '[') brackets_in++; + else if (path[i] == ']') brackets_in--; + if (brackets_in) { + // if we see a slash inside brackets, then we patch it so it doesn't + // confuse the filename object's directory handling. + if ( (path[i] == '/') || (path[i] == '\\') ) + path[i] = '#'; + } + } + to_add.path(path); + to_fill += to_add; + } + return true; +} +#endif // __UNIX__ + +} //namespace. + diff --git a/nucleus/library/processes/process_control.h b/nucleus/library/processes/process_control.h new file mode 100644 index 00000000..c6d25861 --- /dev/null +++ b/nucleus/library/processes/process_control.h @@ -0,0 +1,105 @@ +#ifndef PROCESS_CONTROL_CLASS +#define PROCESS_CONTROL_CLASS + +/*****************************************************************************\ +* * +* Name : process_control * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "process_entry.h" + +#include +#include +#include + +namespace processes { + +// forward. +class process_entry_array; +class process_implementation_hider; + +//! Provides a bridge to the operating system for information on processes. +/*! + This object can query the operating system for the current set of processes + or zap a particular process of interest. +*/ + +class process_control : public virtual basis::nameable +{ +public: + process_control(); + virtual ~process_control(); + + DEFINE_CLASS_NAME("process_control"); + + bool healthy() const { return _healthy; } + //!< returns true if this object should be functional. + /*!< if it failed to construct properly, this returns false. usually a + failure indicates that a required dynamic library is missing, such as + "psapi.dll" on win32. */ + + process_entry query_process(basis::un_int to_query); + //!< returns the information for just one process. + + bool query_processes(process_entry_array &to_fill); + //!< finds the processes that are running and drops them into "to_fill". + + bool zap_process(basis::un_int to_zap); + //!< preemptively zaps the process "to_zap". + /*!< this does not invoke any friendly graceful shut down process, but + merely terminates it if possible. */ + + static bool find_process_in_list(const process_entry_array &processes, + const basis::astring &app_name, structures::int_set &pids); + //!< uses a pre-existing list of "processes" to search for the "app_name". + /*!< if the process is found, true is returned and the "pids" are set to + all entries matching the process name. note that this is an approximate + match for some OSes that have a brain damaged process lister (such as + ms-windows); they have programs listed under incomplete names in some + cases. */ + + void sort_by_name(process_entry_array &to_sort); + // sorts the list by process name. + void sort_by_pid(process_entry_array &to_sort); + // sorts the list by process id. + +private: + process_implementation_hider *_ptrs; //!< our OS baggage. +#ifdef __UNIX__ + mathematics::chaos *_rando; //!< used for process list. +#endif +#ifdef __WIN32__ + bool _use_psapi; //!< true if we should be using the PSAPI on NT and family. +#endif + bool _healthy; //!< true if construction succeeded. + +#ifdef __UNIX__ + bool get_processes_with_ps(process_entry_array &to_fill); + //!< asks the ps program what processes exist. +#endif +#ifdef __WIN32__ + // fill in our function pointers to access the kernel functions appropriate + // for either NT (psapi) or 9x (toolhelp). + bool initialize_psapi_support(); + bool initialize_toolhelp_support(); + + bool get_processes_with_psapi(process_entry_array &to_fill); + //!< uses the PSAPI support for windows NT 4 (or earlier?). + bool get_processes_with_toolhelp(process_entry_array &to_fill); + //!< uses the toolhelp support for windows 9x, ME, 2000. +#endif +}; + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/processes/process_entry.cpp b/nucleus/library/processes/process_entry.cpp new file mode 100644 index 00000000..40cf3589 --- /dev/null +++ b/nucleus/library/processes/process_entry.cpp @@ -0,0 +1,86 @@ +/*****************************************************************************\ +* * +* Name : process_entry * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "process_entry.h" + +#include +#include +#include + +using namespace basis; +using namespace filesystem; + +namespace processes { + +process_entry::process_entry() +: _process_id(0), + _references(0), + _threads(0), + _parent_process_id(0), + _module16(0), + _process_path(new astring) +{} + +process_entry::process_entry(const process_entry &to_copy) +: _process_id(0), + _references(0), + _threads(0), + _parent_process_id(0), + _module16(0), + _process_path(new astring) +{ + operator =(to_copy); +} + +process_entry::~process_entry() +{ + WHACK(_process_path); +} + +void process_entry::text_form(basis::base_string &fill) const +{ + fill = text_form(); +} + +process_entry &process_entry::operator =(const process_entry &to_copy) +{ + if (&to_copy == this) return *this; + _process_id = to_copy._process_id; + _references = to_copy._references; + _threads = to_copy._threads; + _parent_process_id = to_copy._parent_process_id; + *_process_path = *to_copy._process_path; + _module16 = to_copy._module16; + return *this; +} + +const astring &process_entry::path() const { return *_process_path; } + +void process_entry::path(const astring &new_path) +{ *_process_path = new_path; } + +astring process_entry::text_form() const +{ +#ifdef __UNIX__ + filename pat(path()); +#else + filename pat(path().lower()); +#endif + return a_sprintf("%s: pid=%u refs=%u thrd=%u par=%u mod16=%u path=%s", + pat.basename().raw().s(), _process_id, _references, _threads, + _parent_process_id, _module16, pat.dirname().raw().s()); +} + +} //namespace. + diff --git a/nucleus/library/processes/process_entry.h b/nucleus/library/processes/process_entry.h new file mode 100644 index 00000000..7c30b31b --- /dev/null +++ b/nucleus/library/processes/process_entry.h @@ -0,0 +1,67 @@ +#ifndef PROCESS_ENTRY_CLASS +#define PROCESS_ENTRY_CLASS + +/*****************************************************************************\ +* * +* Name : process_entry * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +namespace processes { + +//! Encapsulates information about OS processes. + +class process_entry : public virtual basis::text_formable +{ +public: + basis::un_int _process_id; //!< the OS identifier of this process. + basis::un_int _references; //!< the number of references to (users of) this process. + basis::un_int _threads; //!< the number of threads in use by this process. + basis::un_int _parent_process_id; //!< the process id of the owning process. + basis::un_short _module16; //!< non-zero if this process is a 16-bit application. + + process_entry(); + process_entry(const process_entry &to_copy); + ~process_entry(); + + DEFINE_CLASS_NAME("process_entry"); + + process_entry &operator =(const process_entry &to_copy); + + const basis::astring &path() const; + void path(const basis::astring &new_path); + + basis::astring text_form() const; + //!< returns a descriptive string for the information here. + + void text_form(basis::base_string &fill) const; //!< base class requirement. + +private: + basis::astring *_process_path; +}; + +////////////// + +//! a handy class that implements an array of process entries. + +class process_entry_array : public basis::array {}; + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/rendezvous.cpp b/nucleus/library/processes/rendezvous.cpp new file mode 100644 index 00000000..3f3385e8 --- /dev/null +++ b/nucleus/library/processes/rendezvous.cpp @@ -0,0 +1,211 @@ +/*****************************************************************************\ +* * +* Name : rendezvous * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//note: after repeated investigation, it seems that if we unlink the rendezvous +// file on destruction, then this hoses up any locks attempted ever after. +// instead of waiting for a lock, new attempts think they can go ahead, +// even though someone else might also have been given the lock. it seems +// we cannot remove the files without destroying the semantics. + +#include "rendezvous.h" + +#include +#include +#include +#include +#include + +#ifdef __UNIX__ + #include + #include + #include +#endif + +using namespace basis; +using namespace filesystem; + +namespace processes { + +//#define DEBUG_RENDEZVOUS + // uncomment for a noisier file. + +#undef LOG +#define LOG(tp) printf("%s%s\n", time_stamp::notarize(true).s(), astring(tp).s()) + // we can only use simple logging here since the rendezvous is relied on + // at very low levels and use of a log_base object would cause infinite + // loops. + +// used for the name of a mutex or part of the unix lock filename. +astring general_lock_name(const astring &root_part) +{ return root_part + "_app_lock"; } + +#ifdef __UNIX__ +// the name of the locking file used in unix. +astring unix_rendez_file(const astring &lock_name) +{ + astring clean_name = lock_name; + // remove troublesome characters from the name. + filename::detooth_filename(clean_name); + // make sure our target directory exists. + + // this choice is only user specific. +// astring tmp_dir = portable::env_string("TMP") + "/rendezvous"; + + // this choice uses a system-wide location. + astring tmp_dir = "/tmp/rendezvous"; + + mkdir(tmp_dir.observe(), 0777); + return tmp_dir + "/ren_" + clean_name; +} +#endif + +rendezvous::rendezvous(const astring &root_name) +: _handle(NIL), + _locked(false), + _root_name(new astring(root_name)) +{ +#ifdef DEBUG_RENDEZVOUS + FUNCDEF("constructor"); +#endif + astring lock_name = general_lock_name(root_name); +#ifdef __UNIX__ + astring real_name = unix_rendez_file(lock_name); + FILE *locking_file = fopen(real_name.s(), "wb"); + if (!locking_file) { +#ifdef DEBUG_RENDEZVOUS + LOG(astring("failure to create locking file ") + real_name + + ": " + critical_events::system_error_text(critical_events::system_error()) ); +#endif + return; + } + // success now. + _handle = locking_file; +#endif +#ifdef __WIN32__ + _handle = CreateMutex(NIL, false, to_unicode_temp(lock_name)); + if (!_handle) return; +#endif +} + +rendezvous::~rendezvous() +{ +#ifdef DEBUG_RENDEZVOUS + FUNCDEF("destructor"); + LOG("okay, into destructor for rendezvous."); +#endif +#ifdef __UNIX__ + if (_handle) { + if (_locked) { + int ret = lockf(fileno((FILE *)_handle), F_ULOCK, sizeof(int)); + if (ret) { +#ifdef DEBUG_RENDEZVOUS + LOG("failure to get lock on file."); +#endif + } + _locked = false; // clear our locked status since we no longer have one. + +//note: after repeated investigation, it seems that if we unlink the rendezvous +// file on destruction, then this hoses up any locks attempted ever after. +// instead of waiting for a lock, new attempts think they can go ahead, +// even though someone else might also have been given the lock. it seems +// we cannot remove the files without destroying the semantics. + } + + fclose((FILE *)_handle); + _handle = NIL; + } +#endif +#ifdef __WIN32__ + if (_handle) CloseHandle((HANDLE)_handle); +#endif + WHACK(_root_name); +} + +void rendezvous::establish_lock() { lock(); } + +void rendezvous::repeal_lock() { unlock(); } + +bool rendezvous::healthy() const +{ + return !!_handle; +} + +bool rendezvous::lock(locking_methods how) +{ +#ifdef DEBUG_RENDEZVOUS + FUNCDEF("lock"); +#endif + if (how == NO_LOCKING) return false; + if (!healthy()) return false; +#ifdef __UNIX__ + int command = F_TLOCK; + if (how == ENDLESS_WAIT) command = F_LOCK; + int ret = lockf(fileno((FILE *)_handle), command, sizeof(int)); + if (ret) { +#ifdef DEBUG_RENDEZVOUS + LOG("failure to get lock on file."); +#endif + return false; + } +#ifdef DEBUG_RENDEZVOUS + LOG("okay, got lock on shared mem."); +#endif + _locked = true; + return true; +#endif +#ifdef __WIN32__ + int timing = 0; // immediate return. + if (how == ENDLESS_WAIT) timing = INFINITE; + int ret = WaitForSingleObject((HANDLE)_handle, timing); + if ( (ret == WAIT_ABANDONED) || (ret == WAIT_TIMEOUT) ) return false; + else if (ret != WAIT_OBJECT_0) { +#ifdef DEBUG_RENDEZVOUS + LOG("got an unanticipated error from waiting for the mutex."); +#endif + return false; + } + _locked = true; + return true; +#endif + return false; +} + +void rendezvous::unlock() +{ +#ifdef DEBUG_RENDEZVOUS + FUNCDEF("unlock"); +#endif + if (!healthy()) return; + if (_locked) { +#ifdef __UNIX__ + int ret = lockf(fileno((FILE *)_handle), F_ULOCK, sizeof(int)); + if (ret) { +#ifdef DEBUG_RENDEZVOUS + LOG("failure to get lock on file."); +#endif + } +#endif +#ifdef __WIN32__ + ReleaseMutex((HANDLE)_handle); +#endif + _locked = false; + } else { +#ifdef DEBUG_RENDEZVOUS + LOG("okay, rendezvous wasn't locked."); +#endif + } +} + +} //namespace. + diff --git a/nucleus/library/processes/rendezvous.h b/nucleus/library/processes/rendezvous.h new file mode 100644 index 00000000..b04f3bd4 --- /dev/null +++ b/nucleus/library/processes/rendezvous.h @@ -0,0 +1,76 @@ +#ifndef RENDEZVOUS_CLASS +#define RENDEZVOUS_CLASS + +/*****************************************************************************\ +* * +* Name : rendezvous * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace processes { + +//! An inter-process synchronization primitive. +/*! + A lock can be created that only one process owns at a time; those that do + not acquire the lock can either return immediately or wait until the current + lock owner releases the rendezvous. This is unlike the mutex object in + basis, since mutexes only synchronize within the same application. The + rendezvous can instead allow synchronization of resources between + applications, but this comes at a higher cost per usage. +*/ + +class rendezvous : public virtual basis::base_synchronizer +{ +public: + rendezvous(const basis::astring &root_name); + //!< the healthy() method should be checked to ensure creation succeeded. + + virtual ~rendezvous(); + //!< any lock held is released and the lower level structures freed. + + DEFINE_CLASS_NAME("rendezvous"); + + bool healthy() const; + //!< returns true if the rendezvous object is operable. + /*!< there are cases where creation of the rendezvous might fail; they + can be trapped here. */ + + //! different ways that the lock() attempt can be made. + enum locking_methods { NO_LOCKING, ENDLESS_WAIT, IMMEDIATE_RETURN }; + + bool lock(locking_methods how = ENDLESS_WAIT); + //!< grab the lock, if possible. + /*!< if this is not the first time locking the same rendezvous, that's + fine as long as the number of unlocks matches the number of locks. */ + + void unlock(); + //!< releases a previously acquired lock. + + // these two methods implement the synchronizer_base interface. + virtual void establish_lock(); + virtual void repeal_lock(); + + const basis::astring &root_name() const; + //!< returns the root name passed in the constructor. + +private: + void *_handle; //!< the real OS version of the inter-app lock. + bool _locked; //!< true if the lock was successful and still locked. + basis::astring *_root_name; //!< the identifier for this rendezvous. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/safe_callback.cpp b/nucleus/library/processes/safe_callback.cpp new file mode 100644 index 00000000..ca62be94 --- /dev/null +++ b/nucleus/library/processes/safe_callback.cpp @@ -0,0 +1,174 @@ + + + +/*****************************************************************************\ +* * +* Name : safe_callback * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "safe_callback.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace loggers; +using namespace structures; + +namespace processes { + +////////////// + +callback_data_block::~callback_data_block() {} + +////////////// + +class live_object_info +{ +public: + int _references; // the number of times it's been added to the list. + + live_object_info() : _references(1) {} +}; + +////////////// + +static bool _live_objects_are_gone = false; + // flags when the global_live_objects singleton winks out of existence. this + // should prevent ordering conflicts during the static destructions. + +class global_live_objects : public virtual root_object +{ +public: + global_live_objects() : _objects(rotating_byte_hasher(), 12) {} + // note that we have about a 2 billion callback object limit currently. + + ~global_live_objects() { _live_objects_are_gone = true; } + + DEFINE_CLASS_NAME("global_live_objects"); + + // returns true if the "object" is listed as valid. + bool listed(void *object) { + auto_synchronizer l(_lock); + live_object_info *loi = NIL; + return _objects.find(object, loi); + } + + // adds the "object" to the list, or if it's already there, ups the refcount. + void add(void *object) { + auto_synchronizer l(_lock); + live_object_info *loi = NIL; + if (!_objects.find(object, loi)) { + // this is a new item. + _objects.add(object, new live_object_info); + return; + } + // this item already exists. + loi->_references++; + } + + // reduces the refcount on the "object" and removes it if there are zero + // references. + void remove(void *object) { + auto_synchronizer l(_lock); + live_object_info *loi = NIL; + if (!_objects.find(object, loi)) { + // this item doesn't exist??? bad usage has occurred.. + return; + } + // patch its reference count. + loi->_references--; + if (!loi->_references) { + // ooh, it croaked. get rid of it now. + _objects.zap(object); + } + } + +private: + mutex _lock; // protects our list. + hash_table _objects; + // the valid objects are listed here. +}; + +////////////// + +safe_callback::safe_callback() +: _decoupled(false), + _callback_lock(new mutex) +{ begin_availability(); } + +safe_callback::~safe_callback() +{ + if (!_decoupled) + non_continuable_error(class_name(), "destructor", + "the derived safe_callback has not called end_availability() yet.\r\n" + "this violates caveat two of safe_callback (see header)."); + WHACK(_callback_lock); +} + +SAFE_STATIC(global_live_objects, safe_callback::_invocables, ) + +void safe_callback::begin_availability() +{ + // we don't lock the mutex here because there'd better not already be + // a call to the callback function that happens while the safe_callback + // object is still being constructed...! + + _decoupled = false; // set to be sure we know we ARE hooked in. + if (_live_objects_are_gone) return; // hosed. + _invocables().add(this); // list this object as valid. +} + +void safe_callback::end_availability() +{ + if (_decoupled) return; // never unhook any one safe_callback object twice. + if (_live_objects_are_gone) { + // nothing to unlist from. + _decoupled = true; + return; + } + _callback_lock->lock(); // protect access to this object. + _invocables().remove(this); // unlist this object. + _decoupled = true; // we are now out of the action. + _callback_lock->unlock(); // release lock again. + // we shoot the lock here so that people hear about it immediately. they + // will then be released from their pending but failed callback invocations. + WHACK(_callback_lock); +} + +bool safe_callback::invoke_callback(callback_data_block &new_data) +{ + auto_synchronizer l(*_callback_lock); + // we now have the lock. + if (!_invocables().listed(this)) return false; + // this object is no longer valid, so we must not touch it. + if (_decoupled) return false; + // object is partially decoupled already somehow. perhaps this instance + // has already been connected but another hook-up exists at some place + // else in the derivation hierarchy. they'd better be careful to shut + // down all the safe_callbacks before proceeding to destroy... + // if they do that, they're fine, since the shutdown of any owned data + // members would be postponed until after all the callbacks had been + // removed. + real_callback(new_data); + return true; +} + +} //namespace. + + + diff --git a/nucleus/library/processes/safe_callback.h b/nucleus/library/processes/safe_callback.h new file mode 100644 index 00000000..9ee33d2a --- /dev/null +++ b/nucleus/library/processes/safe_callback.h @@ -0,0 +1,152 @@ +#ifndef SAFE_CALLBACK_CLASS +#define SAFE_CALLBACK_CLASS + +/*****************************************************************************\ +* * +* Name : safe_callback * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace processes { + +// forward. +class callback_data_block; +class global_live_objects; + +//! A reasonably easy way to make callbacks safe from shutdown ordering issues. +/*! + If an object implementing an unsafe, standard callback is destroyed, then + all of the pending callbacks on other threads are jeopardized. Objects with + unsafe objects will not fare well in a multi-threaded program at all. This + class ensures that no callback can ever occur on a dead object, but this + promise is subject to the caveats below. + + Caveat One: + + if you have synchronization control over objects that you own, please + DO NOT have them locked while your callback base class is being destroyed + NOR when you call the end_availability() method. allowing them to be locked + at those times can result in a program deadlock. ensuring this is a simple + matter of having a properly formed destructor, as in caveat two. + + Caveat Two: + + an object that implements safe_callback MUST invoke the end_availability + method as the FIRST thing in its destructor. otherwise, if some portions + of the object are shutdown before the safe_callback is stopped, then the + active callback invocation can have the rug pulled out from under it and + suddenly be working with bad objects. here is an example of a safe + destructor implementation: @code + + my_safe_callback::~my_safe_callback() { + // safely revoke this object's listing before other destructions. + end_availability(); + // destroy other objects now... + WHACK(first_thing); //...etc.... + } @endcode + + note also: if your object owns or is derived from more than one + safe_callback, then ALL of those must have their end_availability methods + invoked before ANY of the other objects owned can be destroyed. we + recommend against deriving from more than one safe_callback just for + simplicity's sake. +*/ + +class safe_callback +{ +public: + safe_callback(); + //!< construction signifies that the callback is now in operation. + + void end_availability(); + //!< prepares to shut down this object. + /*!< This removes the object from the list of available callbacks. this + MUST be called in a derived destructor BEFORE any other objects owned by + the derived class are destroyed. */ + + virtual ~safe_callback(); + //!< actually destroys the object. + /*!< don't allow this to be called without having invoked + end_availability() at the top of your derived class's destructor. + otherwise, you have broken your code by failing caveat two, above. */ + + DEFINE_CLASS_NAME("safe_callback"); + + bool decoupled() const { return _decoupled; } + //!< if true, then end_availability() was already invoked on the object. + /*!< if this is returning true, then one can trust that the object is + properly deregistered and safely decoupled from the callback. a return of + false means that the object still might be hooked into callbacks and it is + not yet safe to destroy the object. */ + + bool invoke_callback(callback_data_block &new_data); + //!< this function is invoked by a user of the safe_callback derived object. + /*!< this performs some safety checks before invoking the real_callback() + method. given the caveats are adhered to, we automatically ensure that the + object to be called actually exists in a healthy state. we also enforce + that the called object cannot be destroyed until after any active + callback invocation is finished, and that any pending callbacks are + rejected once the object is invalid. + true is returned if the safe callback was actually completed. a false + return indicates that the object had already shut down. perhaps that's + a sign that the caller should now remove the object if it hasn't already + been removed externally... certainly if the call is rejected more than + once, there's no reason to keep invoking the callback. + each safe_callback implements its own locking to ensure that the + object is still alive. thus, derived objects don't need to provide some + separate proof of their health. this also allows the derived object + to mostly ignore callback-related locking in its own synchronization + code (mostly--see caveats above). */ + +protected: + virtual void real_callback(callback_data_block &new_data) = 0; + //!< derived classes implement this to provide their callback functionality. + /*!< this call will be prevented from ever occurring on an invalid "this" + pointer (given the caveats above are adhered to). */ + +private: + bool _decoupled; //!< true if we have ended our availability. + basis::mutex *_callback_lock; //!< synchronizes access to this object. + void begin_availability(); + //!< allows the safe_callback derived object to be called. + /*!< if this was never invoked, then attempts to callback are rejected. */ + +public: + global_live_objects &_invocables(); + //!< provides access to the program-wide list of healthy callback objects. + /*!< this should not be used by anyone external to the safe_callback + implementation. */ +}; + +////////////// + +//! a simple place-holder that anonymizes the type passed to the callback. +/*! + the virtual destructor above ensures that RTTI can be used if necessary; + objects of different class types can be differentiated when passed to the + callback. +*/ +class callback_data_block +{ +public: + virtual ~callback_data_block(); +}; + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/safe_roller.cpp b/nucleus/library/processes/safe_roller.cpp new file mode 100644 index 00000000..85dd8acd --- /dev/null +++ b/nucleus/library/processes/safe_roller.cpp @@ -0,0 +1,74 @@ +/*****************************************************************************\ +* * +* Name : safe_roller * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "safe_roller.h" + +#include +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace processes { + +SAFE_STATIC(mutex, __roller_synch, ) + +void safe_add(int &to_change, int addition) +{ + auto_synchronizer l(__roller_synch()); + to_change += addition; +} + +////////////// + +safe_roller::safe_roller(int start_of_range, int end_of_range) +: _rolling(new int_roller(start_of_range, end_of_range)), + _lock(new mutex) +{ +} + +safe_roller::~safe_roller() +{ + WHACK(_rolling); + WHACK(_lock); +} + +int safe_roller::next_id() +{ + _lock->lock(); + int to_return = _rolling->next_id(); + _lock->unlock(); + return to_return; +} + +int safe_roller::current() const +{ + _lock->lock(); + int to_return = _rolling->current(); + _lock->unlock(); + return to_return; +} + +void safe_roller::set_current(int new_current) +{ + _lock->lock(); + _rolling->set_current(new_current); + _lock->unlock(); +} + +} //namespace. + + diff --git a/nucleus/library/processes/safe_roller.h b/nucleus/library/processes/safe_roller.h new file mode 100644 index 00000000..6c9a1643 --- /dev/null +++ b/nucleus/library/processes/safe_roller.h @@ -0,0 +1,76 @@ +#ifndef SAFE_ROLLER_CLASS +#define SAFE_ROLLER_CLASS + +/*****************************************************************************\ +* * +* Name : safe_roller * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace processes { + +//! Implements a thread-safe roller object. +/*! + Integers can be generated by this object without concern for corruption by + multiple threads. +*/ + +class safe_roller +{ +public: + safe_roller(int start_of_range = 0, int end_of_range = MAXINT32); + //!< Provides numbers between the start and end in a thread-safe way. + /*!< constructs a roller that runs from the value "start_of_range" and + will stay smaller than "end_of_range" (unless the initial parameters are + screwed up; this class doesn't validate the start and end of range). + when the roller hits the end of range, then its value is reset to the + "start_of_range" again. */ + + ~safe_roller(); + + int next_id(); + //!< returns a unique (per instance of this type) id. + + int current() const; + //!< returns the current id to be used; be careful! + /*!< this value will be the next one returned, so only look at the + current id and don't use it unwisely. this function is useful if you want + to assign an id provisionally but might not want to complete the + issuance of it. */ + + void set_current(int new_current); + //!< allows the current id to be manipulated. + /*!< this must be done with care lest existing ids be re-used. */ + +private: + structures::int_roller *_rolling; //!< the id generator. + basis::mutex *_lock; //!< thread synchronization. + + // forbidden... + safe_roller(const safe_roller &); + safe_roller &operator =(const safe_roller &); +}; + +////////////// + +void safe_add(int &to_change, int addition); + //!< thread-safe integer addition. + /*!< the number passed in "addition" is atomically added to the number + referenced in "to_change". this allows thread-safe manipulation of + a simple number. */ + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/state_machine.cpp b/nucleus/library/processes/state_machine.cpp new file mode 100644 index 00000000..3704bf98 --- /dev/null +++ b/nucleus/library/processes/state_machine.cpp @@ -0,0 +1,505 @@ +/*****************************************************************************\ +* * +* Name : state_machine * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "state_machine.h" + +#include +#include +#include +#include +#include +#include + +using namespace basis; +using namespace loggers; +using namespace timely; + +namespace processes { + +////////////// + +//#define DEBUG_STATE_MACHINE + // uncomment if you want the debugging version. + +//hmmm: implement logging... +#undef LOG +#ifndef DEBUG_STATE_MACHINE + #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) +#else + #define LOG(to_print) {} +#endif + +////////////// + +// checks whether the machine passed in is valid or not. +#define CHECK_VALID(m) \ + if (!m._current) return false; \ + if (!m._last) return false + +// checks whether the current and next states exist or not. +#define CHECK_STATES \ + if (!current) return false; \ + if (!next) return false; \ + int indy = state_index(current); \ + if (negative(indy)) return false; \ + if (negative(state_index(next))) return false + +// moves to a new state and resets the new state's timestamp. +#define MOVE_STATE(m, next, type, trigger) \ + m._last = m._current; \ + m._current = next; \ + m._type = type; \ + m._trig = trigger; \ + m._start->reset() + +// locates a state or returns. +#define FIND_STATE(state) \ + int indy = state_index(state); \ + if (negative(indy)) return + +////////////// + +struct override { int current; int next; int duration; + override(int _current = 0, int _next = 0, int _duration = 0) + : current(_current), next(_next), duration(_duration) {} +}; + +struct transition_info { + enum transition_type { SIMPLE, RANGE, TIMED }; + transition_type type; + int next_state; + int low_trigger, high_trigger; + int time_span; + + transition_info() {} // blank. + transition_info(int next) : type(SIMPLE), next_state(next) {} + transition_info(int next, int time) : type(TIMED), next_state(next), + time_span(time) {} + transition_info(int next, int low, int high) : type(RANGE), + next_state(next), low_trigger(low), high_trigger(high) {} +}; + +struct state_info { + int state_id; // id for this state. + array transitions; + + state_info() : state_id(0) {} // blank. + state_info(int state_id_in) : state_id(state_id_in) {} +}; + +////////////// + +class state_machine_override_array : public array {}; +class state_machine_state_array : public array {}; + +////////////// + +state_machine::state_machine() +: _current(0), + _last(0), + _trig(0), + _type(SIMPLE), + _start(new time_stamp()), + _name(new astring()), + _overrides(new state_machine_override_array) +{} + +state_machine::state_machine(const state_machine &to_copy) +: root_object(), + _current(0), + _last(0), + _trig(0), + _type(SIMPLE), + _start(new time_stamp()), + _name(new astring()), + _overrides(new state_machine_override_array) +{ *this = to_copy; } + +state_machine::~state_machine() +{ + WHACK(_start); + WHACK(_name); + WHACK(_overrides); +} + +int state_machine::update() { return 0; } + +void state_machine::set_name(const astring &name) { *_name = name; } + +astring state_machine::get_name() const { return *_name; } + +state_machine &state_machine::operator = (const state_machine &to_copy) +{ + if (&to_copy == this) return *this; + _current = to_copy._current; + _last = to_copy._last; + _trig = to_copy._trig; + _type = to_copy._type; + *_start = *to_copy._start; + *_name = *to_copy._name; + *_overrides = *to_copy._overrides; +//careful when overrides becomes hidden internal type. + return *this; +} + +int state_machine::duration_index(int current, int next) const +{ + for (int i = 0; i < _overrides->length(); i++) + if ( ((*_overrides)[i].current == current) + && ((*_overrides)[i].next == next) ) + return i; + return common::NOT_FOUND; +} + +void state_machine::set_state(int new_current, int new_last, int trigger, + transition_types type) +{ + _current = new_current; + _last = new_last; + _trig = trigger; + _type = type; + _start->reset(); +} + +void state_machine::override_timing(int current, int next, int duration) +{ + int indy = duration_index(current, next); + if (non_negative(indy)) { + // found an existing record for this current/next pair. + if (!duration) { + // zero duration means removal. + _overrides->zap(indy, indy); + return; + } + // reset the duration. + (*_overrides)[indy].duration = duration; + return; + } + // no existing record, so add one. + *_overrides += override(current, next, duration); +} + +int state_machine::duration_override(int current, int next) const +{ + int indy = duration_index(current, next); + if (negative(indy)) return 0; + return (*_overrides)[indy].duration; +} + +time_stamp state_machine::start() const { return *_start; } + +////////////// + +transition_map::transition_map() +: _valid(false), + _start_state(0), + _state_list(new state_machine_state_array) +{} + +transition_map::~transition_map() +{ + _valid = false; + WHACK(_state_list); +} + +// informational functions: + +int transition_map::states() const { return _state_list->length(); } + +int transition_map::state_index(int state_id) const +{ + for (int i = 0; i < states(); i++) + if ((*_state_list)[i].state_id == state_id) return i; + return common::NOT_FOUND; +} + +int transition_map::transition_index(int state_index, int next, int &start) +{ + bounds_return(state_index, 0, states() - 1, common::BAD_INPUT); + state_info &state = (*_state_list)[state_index]; + bounds_return(start, 0, state.transitions.length() - 1, common::BAD_INPUT); + // loop over the transitions by using our external index. + for (start = start; start < state.transitions.length(); start++) + if (state.transitions[start].next_state == next) { + start++; // position it after this index. + return start - 1; // return this index. + } + return common::NOT_FOUND; +} + +// configurational functions: + +void transition_map::reconfigure() { _valid = false; } + +outcome transition_map::validate(int &examine) +{ + // check for a bad starting state... + examine = _start_state; + FIND_STATE(_start_state) BAD_START; + + if (!check_reachability(examine)) return UNREACHABLE; + // a state is unreachable from the starting state. + if (!check_overlapping(examine)) return OVERLAPPING_RANGES; + // bad (overlapping) ranges were found in one state. + _valid = true; // set us to operational. + return OKAY; +} + +bool transition_map::add_state(int state_number) +{ + if (valid()) return false; // this is operational; no more config! + if (!state_number) return false; // zero is disallowed. + if (non_negative(state_index(state_number))) return false; // already exists. + *_state_list += state_info(state_number); + return true; +} + +bool transition_map::set_start(int starting_state) +{ + if (valid()) return false; // this is operational; no more config! + if (!starting_state) return false; + + FIND_STATE(starting_state) false; // doesn't exist. + + _start_state = starting_state; + return true; +} + +bool transition_map::add_simple_transition(int current, int next) +{ + if (valid()) return false; // this is operational; no more config! + CHECK_STATES; + (*_state_list)[indy].transitions += transition_info(next); + return true; +} + +bool transition_map::add_range_transition(int current, int next, int low, int high) +{ + if (valid()) return false; // this is operational; no more config! + CHECK_STATES; + (*_state_list)[indy].transitions += transition_info(next, low, high); + return true; +} + +bool transition_map::add_timed_transition(int current, int next, int length) +{ + if (valid()) return false; // this is operational; no more config! + CHECK_STATES; + state_info &found = (*_state_list)[indy]; + // locates any existing timed transition and re-uses its slot. + for (int i = 0; i < found.transitions.length(); i++) + if (found.transitions[i].type == transition_info::TIMED) { + found.transitions[i] = transition_info(next, length); + return true; + } + // no existing timed transition found, so add a new one. + (*_state_list)[indy].transitions += transition_info(next, length); + return true; +} + +// operational functions: + +bool transition_map::make_transition(state_machine &m, int next) +{ + if (!valid()) return false; // this is not operational yet! + CHECK_VALID(m); +#ifdef DEBUG_STATE_MACHINE + if (negative(state_index(m._current))) + LOG(astring("(%s) transition_map::make_transition: bad logic error; machine's " + "state is missing.", m._name->s())); + if (negative(state_index(next))) + LOG(astring("(%s) transition_map::make_transition: next state parameter is invalid.", + m._name->s())); +#endif + FIND_STATE(m._current) false; // bad next state. + int start = 0; + if (negative(transition_index(indy, next, start))) return false; + // no transition exists for that next state. + MOVE_STATE(m, next, state_machine::SIMPLE, 0); + int trig = m.update(); + if (trig) return pulse(m, trig); + return true; +} + +bool transition_map::pulse(state_machine &m, int trigger) +{ + if (!valid()) return false; // this is not operational yet! + CHECK_VALID(m); +#ifdef DEBUG_STATE_MACHINE + if (negative(state_index(m._current))) + LOG(astring("(%s) transition_map::pulse: bad logic error; state is missing.", m._name->s())); +#endif + FIND_STATE(m._current) false; // logic error! + state_info &found = (*_state_list)[indy]; + for (int i = 0; i < found.transitions.length(); i++) { + if (found.transitions[i].type == transition_info::RANGE) { + // found the right type of transition. + transition_info &tran = found.transitions[i]; + if ( (tran.low_trigger <= trigger) + && (tran.high_trigger >= trigger) ) { + // found a transition with an acceptable range. + MOVE_STATE(m, tran.next_state, state_machine::RANGE, trigger); + int trig = m.update(); + if (trig) return pulse(m, trig); + return true; + } + } + } + return false; +} + +bool transition_map::time_slice(state_machine &m) +{ + if (!valid()) return false; // this is not operational yet! + CHECK_VALID(m); + +#ifdef DEBUG_STATE_MACHINE + if (negative(state_index(m._current))) + LOG(astring("(%s) transition_map::time_slice: bad logic error; state " + "is missing.", m._name->s())); +#endif + FIND_STATE(m._current) false; // logic error! + + state_info &found = (*_state_list)[indy]; + for (int i = 0; i < found.transitions.length(); i++) { + if (found.transitions[i].type == transition_info::TIMED) { + // found the right type of transition. + transition_info &tran = found.transitions[i]; + int duration = tran.time_span; + int override = m.duration_override(m._current, tran.next_state); + if (override) duration = override; + if (*m._start < time_stamp(-duration)) { + // found a transition with an expired time. + MOVE_STATE(m, tran.next_state, state_machine::TIMED, 0); + int trig = m.update(); + if (trig) return pulse(m, trig); + return true; + } + } + } + return false; +} + +bool transition_map::reset(state_machine &m) +{ + if (!valid()) return false; // this is not operational yet! + m._current = _start_state; + m._last = _start_state; + m._trig = 0; + m._type = state_machine::SIMPLE; + m._start->reset(); + return true; +} + +bool transition_map::check_overlapping(int &examine) +{ + FUNCDEF("check_overlapping"); + examine = 0; + for (int i = 0; i < states(); i++) { + examine = i; + for (int j = 0; j < (*_state_list)[i].transitions.length(); j++) { + transition_info &a = (*_state_list)[i].transitions[j]; + if (a.type != transition_info::RANGE) continue; + for (int k = j + 1; k < (*_state_list)[i].transitions.length(); k++) { + transition_info &b = (*_state_list)[i].transitions[k]; + if (b.type != transition_info::RANGE) continue; + if ( (b.low_trigger <= a.low_trigger) + && (b.high_trigger >= a.low_trigger) ) { + LOG(astring("intersecting range on low end!")); + return false; + } + if ( (b.low_trigger <= a.high_trigger) + && (b.high_trigger >= a.high_trigger) ) { + LOG(astring("intersecting range on high end!")); + return false; + } + } + } + } + return true; +} + +bool transition_map::check_reachability(int &examine) +{ + FUNCDEF("check_reachability"); + examine = 0; + + // list_to_add: the list of states that are definitely reachable and that + // need to be considered. + int_array list_to_add; + list_to_add += _start_state; + + // already_checked: those states that have already been completely considered + // as to their reachability. + array already_checked(states()); + for (int i = 0; i < already_checked.length(); i++) + already_checked[i] = false; + + while (list_to_add.length()) { + // the next state that IS reachable has all of the states reachable from + // it added to the list of states that are to be checked... + examine = list_to_add[0]; + int indy = state_index(examine); + if (negative(indy)) { + LOG(a_sprintf("bad state index for state %d!", examine)); + return false; + } +#ifdef DEBUG_STATE_MACHINE + LOG(a_sprintf("state to add is %d, list size=%d.", examine, + list_to_add.length())); +#endif + // delete the item that we are now checking. + list_to_add.zap(0, 0); + + // check whether this item has already had its kids (those states reachable + // from it) added to the list to add. if so, skip it. + if (already_checked[indy]) continue; + + // update the information for this state we are considering in the list of + // already checked states. + already_checked[indy] = true; + + state_info &found = (*_state_list)[indy]; + + // all states this one can reach are added to the list to check. + for (int i = 0; i < found.transitions.length(); i++) { +#ifdef DEBUG_STATE_MACHINE + LOG(astring("checking transition %d: ", i)); +#endif + int now_reachable = found.transitions[i].next_state; +#ifdef DEBUG_STATE_MACHINE + LOG(astring("now reaching %d.", now_reachable)); +#endif + if (now_reachable == examine) continue; + else { + int indy = state_index(now_reachable); + if (already_checked[indy]) continue; + } + list_to_add += now_reachable; + } + } +#ifdef DEBUG_STATE_MACHINE + LOG("done checking reachability."); +#endif + for (int j = 0; j < already_checked.length(); j++) + if (!already_checked.get(j)) { + examine = (*_state_list)[j].state_id; + LOG(a_sprintf("state %d is not reachable", examine)); + return false; + } + return true; // all states are reachable. +} + +} //namespace. + diff --git a/nucleus/library/processes/state_machine.h b/nucleus/library/processes/state_machine.h new file mode 100644 index 00000000..3d2761e8 --- /dev/null +++ b/nucleus/library/processes/state_machine.h @@ -0,0 +1,348 @@ +#ifndef STATE_MACHINE_CLASS +#define STATE_MACHINE_CLASS + +/*****************************************************************************\ +* * +* Name : state_machine * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace processes { + +class state_machine_override_array; +class state_machine_state_array; + +//! Monitors objects with multiple states and the transitions between states. +/*! + A state machine is a computational abstraction for any control process + that has discrete states and transitions between the states. + As used here, the "state_machine" is a base class for objects that can be + manipulated by the "transition_map" class. Your transition map must be + validated and you must use it to reset() your state_machine object before + any activity can occur. (One aspect of validation is that all states must be + reachable from the user-specified starting state. The other requirements + are documented below for transition_map.) Once validation is done, the + transition map manages the machine through the various states that are + defined and applies trigger values to modify the current state when asked + to do so. It also applies any defined timed transitions automatically. + This implementation of state machines is configured once (by defining the + transition_map object), but then the machines can be run repeatedly (via + the state_machine object). +*/ + +class state_machine : public virtual basis::root_object +{ +public: + state_machine(); + //!< sets up the machine in a blank state. + + state_machine(const state_machine &to_copy); + //!< copies the machine provided in "to_copy". + + virtual ~state_machine(); + + DEFINE_CLASS_NAME("state_machine"); + + state_machine &operator =(const state_machine &to_copy); + //!< assigns this to a copy of the machine provided in "to_copy". + + virtual int update(); + //!< this is the main implementation function provided by derived classes. + /*!< this is implemented by derived classes that want to perform an action + when the state machine is pulsed (see below in transition_map), which is + why it is not pure virtual; a state machine might still be useful as a + state tracking object rather than needing to implement extended + functionality. this function is invoked whenever the state changes due to + a timed, range based or simple transition. one must be careful when + servicing this transition not to enmesh oneself in a snarled invocation + situation; if make_transition or time_slice or pulse are invoked from this + update function and conditions are right for another transition, then the + update function will be re-entered recursively. if the value returned + from update is non-zero, it is used to pulse the state machine again as a + new trigger value (this is safer than explicitly calling one of the + transition causing functions). note that this assumes that zero is an + invalid trigger. if your triggers include zero as a valid value, don't + try returning it through update; use pulse to force the triggering to + accept the invalid trigger instead. */ + + int current() const { return _current; } + //!< returns the current state. + /*!< NOTE: a zero value for a state means that it is uninitialized. */ + + int last() const { return _last; } + //!< returns the last active state. + + int trigger() const { return _trig; } + //!< returns the trigger that caused this state. + /*!< this is only meaningful when the transition was caused by a range + transition. if it was, then ranged() will return true. */ + + enum transition_types { SIMPLE, RANGE, TIMED }; + //!< the three types of transitions supported. + + bool simple() const { return _type == SIMPLE; } + //!< returns true if the last transition was a simple type. + bool ranged() const { return _type == RANGE; } + //!< returns true if the last transition was a ranged type. + bool timed() const { return _type == TIMED; } + //!< returns true if the last transition was a timed type. + + void set_state(int new_current, int new_last, int trigger, + transition_types type); + //!< sets the current state to "new_current" and the previous state to "new_last". + /*!< the "trigger" causing the transition, if any, will be stored also. + the "type" of transition is stored also. the time stamp for time spent in + the current state is reset. be very careful with this; if the two states + do not conform to the legal transitions in your map, then the state + machine will not behave properly. */ + + timely::time_stamp start() const; + //!< start time for the current state. + /*!< this records how long we've been in this state. */ + + void set_name(const basis::astring &name); + //!< sets the name to be printed in any debugging information for "this". + + basis::astring get_name() const; + //!< retrieves the object's current name. + + void override_timing(int current, int next, int duration); + //!< modifies the recorded timing for timed transitions. + /*!< this overrides the transition_map's time-out value for the transition + between the states "current" and "next". a time-out of "duration" + will be used instead of whatever was specified when the map was set + up. to erase an override, use zero as the "duration". */ + + int duration_override(int current, int next) const; + //!< reports if there's a duration override for a timed transition. + /*!< this returns the amount of time that this particular state_machine is + allowed to exist in the "current" state before progressing to the "next" + state. this has nothing to do with the transition_map; it is valid only + for this state_machine object. zero is returned if no override exists. */ + +private: + friend class transition_map; + //!< manager buddy object. + int _current; //!< the current state of the state machine. + int _last; //!< the previous state for the state machine. + int _trig; //!< the last trigger value, if _ranged is true. + transition_types _type; //!< what kind of transition just occurred. + timely::time_stamp *_start; //!< the time this state started. + basis::astring *_name; //!< the name this state_machine reports itself as. + state_machine_override_array *_overrides; //!< the timing overrides. + + int duration_index(int current, int next) const; + //!< finds the index of a duration override in our list. + /*!< this locates the duration override specified for the transition + between "current" and "next". it returns the index of that override, or + a negative number if the override is not found. */ +}; + +////////////// + +//! The transition_map manages state machines and causes state changes to occur. +/*! + The transition_map is a heavyweight class that is a repository for all + information about transitions and which manages pushing the state machines + through the proper states. + + The transition_map guarantees these characteristics: + + 0) the below characteristics are checked (in validate) and no state + machine object is allowed to operate until they are satisfied, + + 1) the machine starts in the specified starting state, + + 2) the current state is always one that has been added and approved, + + 3) transitions are allowed between states only if the transition has + been added and approved, + + 4) the update() function is invoked every time the machine hits a + transition between states (even if it is a transition to the same + state), + + 5) range transitions are non-intersecting within one state, +*** 5 is unimplemented *** + + 6) all states are reachable from the starting state by some valid + transition, and + + 7) each state has no more than one timed transition. + + if any of these conditions are violated, then validate() will fail. + the machine will also not operate properly (at all) until the conditions + are satisfied by validate(). the states and transitions should + thus be carefully examined before turning them into a state machine.... +*/ + +class transition_map : public virtual basis::root_object +{ +public: + transition_map(); + virtual ~transition_map(); + + // informational functions... + + DEFINE_CLASS_NAME("transition_map"); + + bool valid() const { return _valid; } + //!< returns true if the transition_map is valid and ready for operation. + /*!< once the validate() call has succeeded and valid() is true, no more + configuration functions (see below) may be called until the reconfigure() + function is invoked. */ + + int states() const; + //!< returns the current number of states. + + // validation functions... + + enum outcomes { + OKAY = basis::common::OKAY, + DEFINE_OUTCOME(BAD_START, -49, "The start state has not been properly " + "specified"), + DEFINE_OUTCOME(OVERLAPPING_RANGES, -50, "The ranges overlap for two " + "transitions from a state"), + DEFINE_OUTCOME(UNREACHABLE, -51, "There is an unreachable state in the map") + }; + basis::outcome validate(int &examine); + //!< checks to that all required transition conditions are satisfied. + /*!< OKAY is returned if they are and the map is then ready to operate. + other outcomes are returned if one or more of the conditions are not met: + BAD_START means that the starting state is badly specified. + OVERLAPPING_RANGES means that one state has two transitions that do not + have mutually exclusive ranges. UNREACHABLE means that a state is not + reachable from the starting state. for all of these cases, the "examine" + parameter is set to a state related to the problem. */ + + void reconfigure(); + //!< puts the transition_map back into an unvalidated state. + /*!< this allows the characteristics of the map to be changed. validate() + must be called again before resuming operation using this map. */ + + // configuration functions... + + // NOTE: all of the functions below will fail if the transition_map has + // already been validated previously and reconfigure() has not been called. + + // NOTE: a zero value for a state means that it is uninitialized. thus, zero + // is never allowed as a value for a state or a transition endpoint. zero is + // grudgingly allowed as a trigger value, although that interferes with the + // state_machine object's update() method. + + bool add_state(int state_number); + //!< registers a legal state in the transition_map. + /*!< no action will be taken for any state until it is registered. the + operation returns true for successful registration and false for errors + (such as when a state is already registered or is invalid). */ + + bool set_start(int starting_state); + //!< assigns a state as the first state. + /*!< if the "starting_state" does not already exist, it is an error and + false is returned. */ + + bool add_simple_transition(int current, int next); + //!< adds a transition between "current" and "next". + /*!< it is an error to use either an invalid "current" state or an invalid + "next" state. these errors are detected during the validate() function. + this type of transition is unspecialized--it does not respond to triggers + and does not occur due to timing. to make a state_machine undergo this + transition, make_transition() must be used. */ + + bool add_range_transition(int current, int next, int low, int high); + //!< adds a transition that listens to triggers in the pulse() method. + /*!< this uses "low" and "high" as the inclusive range of triggers that + will cause a transition to the "next" state when a state_machine is pulsed + in the "current" state. it is erroneous for these trigger values to + intersect with other values in the same "current" state. */ + bool add_timed_transition(int current, int next, int duration); + //!< adds a transition that occurs after a timeout with no other activity. + /*!< adds a timed transition to the "next" state when the "current" state + has lasted "duration" milliseconds. this relies on the time_slice function + being called periodically, with appropriate regularity for the specified + "duration" (e.g., if one's time-out is 100 ms, then one might want to call + time_slice() every 20 ms or so to ensure that the transition is at most 20 + ms late in firing. NOTE: this is not an argument for calling time_slice() + as fast as possible; it is an argument for realizing that the "duration" + must be carefully considered to meet one's timing deadlines). */ + + // transition functions... + + // NOTE: all of these actions will fail if the transition_map has not been + // validated yet. + + bool make_transition(state_machine &m, int next); + //!< changes the state to the "next" listed for "m" given the current state. + /*!< it is an error to make a transition that has not been specified + through an add_X() transition function (false is returned). if the + transition succeeds, then the current_state will be "next". */ + + bool pulse(state_machine &m, int trigger); + //!< applies a "trigger" to possibly cause a range transition. + /*!< this causes the state_machine to accept the "trigger" as input and + perform at least one traversal of the transition_map. if the trigger + value is found in one of the range transitions for the current state, then + the next state specified in that transition becomes the current state and + the update() function is invoked (and true is returned). if the trigger + is not found in any range transition, then false is returned. */ + + bool time_slice(state_machine &m); + // allows the transition_map to process any timed transitions that may be + // required for the state_machine "m". the return value is true if such + // a transition was found. + + bool reset(state_machine &m); + // resets the state_machine to the starting state in the transition_map. + // the update function is NOT invoked at the time of the reset. true is + // returned if the reset was successful; it would fail if the transition + // map has not been validated yet. + +private: + bool _valid; //!< records whether we've been validated or not. + int _start_state; //!< remembers the default starting state. + state_machine_state_array *_state_list; + //!< the list of transitions between states. + + bool check_overlapping(int &examine); + //!< a validation function that looks for erroneous range transitions. + /*!< this returns true if there are no overlapping ranges found in the + range transitions for each state. */ + + bool check_reachability(int &examine); + //!< returns true if all states are reachable from the starting state. + + int state_index(int state_id) const; + //!< returns the index of "state_id" in states, if it exists. + + int transition_index(int state_index, int next, int &start); + //!< locates a transition into "next" for a state in our list. + /*!< returns the index of a transition between the state at "state_index" + and the "next" state by starting the search at index "start". if there + is no matching transition from the "start" index on in the transition + list, then a negative number is returned. "start" is updated to point + to the index just after a found transition, or to some random number if + the index is not found. */ + + bool check_states(); + //!< returns false if the current machine is hosed up. + + // disallowed functions for now: + transition_map(const transition_map &); + transition_map &operator =(const transition_map &); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/processes/thread_cabinet.cpp b/nucleus/library/processes/thread_cabinet.cpp new file mode 100644 index 00000000..19bc679f --- /dev/null +++ b/nucleus/library/processes/thread_cabinet.cpp @@ -0,0 +1,231 @@ +/*****************************************************************************\ +* * +* Name : thread_cabinet * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "ethread.h" +#include "thread_cabinet.h" + +#include +#include +#include +#include + +#undef LOCKIT +#define LOCKIT auto_synchronizer l(*_lock) + // grabs the mutex for access to the list. + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +//#define DEBUG_THREAD_CABINET + // uncomment for noisier version. + +using namespace basis; +using namespace loggers; +using namespace structures; +using namespace timely; + +namespace processes { + +class thread_record +{ +public: + ethread *_thread; + unique_int _id; + + thread_record(const unique_int &id, ethread *t) + : _thread(t), _id(id) {} + + ~thread_record() { + _thread->stop(); + WHACK(_thread); + } +}; + +////////////// + +class thread_amorph : public amorph +{ +public: +}; + +////////////// + +thread_cabinet::thread_cabinet() +: _lock(new mutex), + _threads(new thread_amorph), + _next_id(new int_roller(1, MAXINT32 - 1)), + _no_additions(0) +{ +} + +thread_cabinet::~thread_cabinet() +{ + WHACK(_threads); + WHACK(_lock); + WHACK(_next_id); +} + +int thread_cabinet::threads() const { return _threads->elements(); } + +unique_int thread_cabinet::add_thread(ethread *to_add, bool start_it, + void *parm) +{ +#ifdef DEBUG_THREAD_CABINET + FUNCDEF("add_thread"); +#endif + LOCKIT; + if (_no_additions) { +#ifdef DEBUG_THREAD_CABINET + LOG("no additions flag is true; destroying the thread and failing out."); +#endif + // can't just leave it unhooked hanging out in space... + WHACK(to_add); + return 0; + } + int use_id = _next_id->next_id(); + if (start_it) { + to_add->start(parm); + } else { +#ifdef DEBUG_THREAD_CABINET + if (to_add->thread_finished()) + LOG(a_sprintf("thread %x is not going to be started and it " + "hasn't started yet!", to_add)); +#endif + } + _threads->append(new thread_record(use_id, to_add)); + return use_id; +} + +bool thread_cabinet::any_running() const +{ + LOCKIT; + for (int i = 0; i < _threads->elements(); i++) { + if (_threads->borrow(i)->_thread->thread_started()) return true; + } + return false; +} + +void thread_cabinet::start_all(void *ptr) +{ + LOCKIT; + for (int i = 0; i < _threads->elements(); i++) { + if (_threads->borrow(i)->_thread->thread_finished()) { + _threads->borrow(i)->_thread->start(ptr); + } + } +} + +void thread_cabinet::cancel_all() +{ + FUNCDEF("cancel_all"); + { + LOCKIT; // short lock. + _no_additions++; // keep people from adding new threads. + for (int i = 0; i < _threads->elements(); i++) { + _threads->borrow(i)->_thread->cancel(); + } + } + LOCKIT; + _no_additions--; // allow new threads again. + if (_no_additions < 0) + continuable_error(class_name(), func, "error in logic regarding " + "no additions."); +} + +void thread_cabinet::stop_all() +{ + FUNCDEF("stop_all"); + { + LOCKIT; // short lock. + _no_additions++; // keep people from adding new threads. + } + cancel_all(); // signal all threads to leave. + // pause to give them a little while to leave. + time_control::sleep_ms(20); + while (true) { + LOCKIT; // short lock. + if (!_threads->elements()) break; // done; nothing left. + clean_debris(); // remove any that did stop. + time_control::sleep_ms(20); // snooze for a short while. + } + LOCKIT; + _no_additions--; // allow new threads again. + if (_no_additions < 0) + continuable_error(class_name(), func, "error in logic regarding " + "no additions."); +} + +bool thread_cabinet::zap_thread(const unique_int &to_whack) +{ + LOCKIT; + for (int i = 0; i < _threads->elements(); i++) { + if (_threads->borrow(i)->_id == to_whack) { + // this is the one they want zapped. + _threads->zap(i, i); + return true; + } + } + return false; +} + +bool thread_cabinet::cancel_thread(const unique_int &to_cancel) +{ + LOCKIT; + for (int i = 0; i < _threads->elements(); i++) { + if (_threads->borrow(i)->_id == to_cancel) { + // this is the one to signal regarding its own demise. + _threads->borrow(i)->_thread->cancel(); + return true; + } + } + return false; +} + +void thread_cabinet::clean_debris() +{ +#ifdef DEBUG_THREAD_CABINET + FUNCDEF("clean_debris"); +#endif + LOCKIT; + for (int i = 0; i < _threads->elements(); i++) { + if (_threads->borrow(i)->_thread->thread_finished()) { + // this one's no longer doing anything. +#ifdef DEBUG_THREAD_CABINET + LOG(a_sprintf("clearing thread %x out.", _threads->borrow(i)->_thread)); +#endif + _threads->zap(i, i); + i--; // skip back before the whack. + } + } +} + +int_set thread_cabinet::thread_ids() const +{ + LOCKIT; + int_set to_return; + for (int i = 0; i < _threads->elements(); i++) + to_return += _threads->borrow(i)->_id.raw_id(); + return to_return; +} + +ethread *thread_cabinet::get_thread(int index) +{ + LOCKIT; + thread_record *rec = _threads->borrow(index); + if (rec) return rec->_thread; + return NIL; +} + +} //namespace. + diff --git a/nucleus/library/processes/thread_cabinet.h b/nucleus/library/processes/thread_cabinet.h new file mode 100644 index 00000000..f8e94e73 --- /dev/null +++ b/nucleus/library/processes/thread_cabinet.h @@ -0,0 +1,104 @@ +#ifndef THREAD_CABINET_CLASS +#define THREAD_CABINET_CLASS + +/*****************************************************************************\ +* * +* Name : thread_cabinet * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +// forward. +#include "ethread.h" + +#include +#include +#include +#include + +namespace processes { + +class thread_amorph; + +//! Manages a collection of threads. +/*! + The thread cabinet allows one to corral a bunch of threads in one place + and treat them as a group if necessary. +*/ + +class thread_cabinet +{ +public: + thread_cabinet(); + virtual ~thread_cabinet(); + + DEFINE_CLASS_NAME("thread_cabinet"); + + structures::unique_int add_thread(ethread *to_add, bool start_it, void *parm); + //!< adds a thread to be managed by the thread_cabinet. + /*!< the thread cabinet takes over responsibility for the thread "to_add". + if "start_it" is true, then the thread is started. otherwise it is + left in whatever state it was in. the "parm" is passed to the thread's + start() method. */ + + bool zap_thread(const structures::unique_int &to_whack); + //!< removes the thread with the id "to_whack". + /*!< if it's found and stopped and removed, true is returned. note that + if the thread is found, then this will wait until the thread exits before + whacking it. */ + + bool cancel_thread(const structures::unique_int &to_cancel); + //!< shuts down the thread "to_cancel" as quickly as possible. + /*!< this calls the cancel() method on the thread "to_cancel", which tells + the thread to stop as soon as possible. once it has stopped, the + clean_debris() method will throw it and other stopped threads out. */ + + int threads() const; //!< number of threads being managed here. + + structures::int_set thread_ids() const; + //!< returns the identifiers of all threads managed by this object. + + bool any_running() const; + //!< returns true if any threads are currently running. + + void start_all(void *pointer); + //!< cranks up any threads that are not already running. + /*!< the "pointer" will be provided to any threads that are started. */ + + void cancel_all(); + //!< signals to all threads that they should exit as soon as possible. + /*!< this does not actually wait for them to exit. */ + + void stop_all(); + //!< makes all of the threads quit. + /*!< they are cleaned up after they have stopped running also. any + attempts to add threads while this method is operating will be rejected. */ + + ethread *get_thread(int index); + //!< this returns the thread at "index" in our list. + /*!< note that this is not safe to use if other threads could be removing + threads from the cabinet or calling clean_debris(). */ + + void clean_debris(); + //!< clean out threads that have finished. + /*!< note that if threads were added to the list without starting them, + then these get cleaned out also. */ + +private: + basis::mutex *_lock; //!< synchronizes our thread list. + thread_amorph *_threads; //!< the list of threads we're managing. + structures::int_roller *_next_id; //!< the id to be assigned to the next thread. + int _no_additions; //!< true if we're not allowed to add threads currently. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/amorph.h b/nucleus/library/structures/amorph.h new file mode 100644 index 00000000..d2104c70 --- /dev/null +++ b/nucleus/library/structures/amorph.h @@ -0,0 +1,520 @@ +#ifndef AMORPH_CLASS +#define AMORPH_CLASS + +/*****************************************************************************\ +* * +* Name : amorph * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "object_packers.h" + +#include +#include +#include +#include + +//! A dynamic container class that holds any kind of object via pointers. +/*! + The object is considered owned by the amorph unless re-acquired from it, + and will be deleted when the amorph is destroyed. + + An amorph has a specified number of fields at any one time, but the number + can be changed with "zap", "insert" and "adjust". Fields in the amorph + are either full or empty, where an empty field in the amorph has NIL as + its content. "put" adds a new field to the amorph. "get" retrieves the + contents of a field as a constant. "acquire" is used to check a field + out of the amorph, meaning that the amorph no longer possesses that field. + The legal range of indices in an amorph is from 0 through + "elements() - 1". In general, a range violation for an index is + treated as an invalid request and is ignored (although BAD_INDEX is + returned by functions with compatible return values). + + Model: + + The policy followed in amorph is that once an object is checked in with + "put" or "append", then the amorph owns that object. The object must not + be destroyed externally, because the amorph will automatically destroy + the object when either: 1) the amorph itself is destroyed, or 2) another + object is entered into the same field. If the stored object must be + destroyed externally, then it should be checked out of the amorph with + "acquire"; after that, the pointer may be deleted. "get" and "borrow" + return a pointer to the stored item while leaving it checked in to the + amorph. it is safe to modify what was "borrow"ed or to look at what one + "get"s, but do not destroy the pointers returned from either method. +*/ + +namespace structures { + +template +class amorph : private basis::array +{ +public: + amorph(int elements = 0); + //!< constructs an amorph capable of holding "elements" pointers. + + ~amorph(); + + int elements() const { return this->length(); } + //!< the maximum number of elements currently allowed in this amorph. + + int valid_fields() const { return _fields_used; } + //!< Returns the number of fields that have non-NIL contents. + /*!< This might be different from the number of total elements. */ + + void adjust(int new_max); + //!< Changes the maximum number of elements for this amorph. + /*!< If the new number is smaller than the original, then the fields at + index "new_maximum" and upwards are thrown away. existing fields are + kept. */ + + void reset(int new_maximum = 0); + //!< like adjust but doesn't keep existing contents. + + basis::outcome put(int field, const contents *data); + //!< Enters an object into the field at index "field" in the amorph. + /*!< If "data" is NIL, then the field is cleared. The amorph considers the + pointer "data" to be its own property after put is invoked; "data" + should not be destructed since the amorph will automatically do so. + This restriction does not hold if the object is checked back out of + the amorph with acquire(). */ + + basis::outcome append(const contents *data); + //!< puts "data" on the end of this amorph. + /*!< adds an element to the end of the amorph by increasing the amorph size + (with "adjust") and putting the element into the new spot (with "put"). */ + + basis::outcome operator += (const contents *data) { return append(data); } + //!< a synonym for append. + + //! Returns a constant pointer to the information at the index "field". + /*! If no information is stored or the field is out range, then NIL is + returned. */ + const contents *get(int field) const; + //! Returns a pointer to the information at the index "field". + /*! Also returns NIL for invalid indexes. DO NOT destroy the returned + pointer; it is still owned by the amorph. */ + contents *borrow(int field); + + //! synonym for get. + const contents *operator [] (int field) const { return get(field); } + //! synonym for borrow. + contents *operator [] (int field) { return borrow(field); } + + contents *acquire(int field); + //!< Retrieves a "field" from the amorph, taking responsibility for it back. + /*!< This function is similar to get, except that the contents are pulled + out of the amorph. The contents will no longer be destroyed when the + amorph is destroyed. To store the modified contents again, use put, + and then the amorph will take over management of the contents again. + Note that the index is not zapped with this function; the acquired + "field" will simply hold a null pointer. */ + + basis::outcome clear(int field); + //!< Clears the contents of the field specified. + /*!< Clearing an empty field has no effect. Clearing an invalid field has + no effect. NOTE: clearing the contents means that the contents are + destroyed, not just disconnected. */ + + void clear_all(); + //!< Clears every field in the amorph. + + basis::outcome zap(int start, int end); + //!< Removes a range of indices from the amorph. + /*!< This does not just clear the field associated with the specified index + as "clear" does, it actually changes the number of total elements by + removing the indices from the amorph. The new amorph contains the old + elements up to just before the "start" and from the "end" + 1 through the + end of the amorph. AMORPH_BAD_INDEX is returned if either index is out of + range. If the zap succeeds, then AMORPH_OKAY is returned, even if the + "end" is less than the "start". */ + + basis::outcome insert(int position, int lines_to_add); + //!< Adds "lines_to_add" indices to the amorph at the index "position". + /*!< If "lines_to_add" is non-positive, the request is ignored. Inserting + at a position beyond the bounds of the array is ignored, but a position AT + elements() is allowed (it is an append...). */ + + int find_empty(basis::outcome &o) const; + //!< Returns the index of a free field if there are any. + /*!< The returned index is invalid if the "o" is IS_FULL. */ + + const contents *next_valid(int &field) const; + //!< Returns the contents of the next valid element at or after "field". + /*!< "field" is set to the location where an entry was found, if one was + actually found. If none exists at "field" or after it, then NIL is + returned. */ + + int find(const contents *to_locate, basis::outcome &o); + //!< Searches the amorph for the contents specified. + /*!< Since only pointers to the contents are maintained, the search is + based on finding a pointer in the amorph that is identical to "to_locate". + if "o" is OKAY, then the index of the entry is returned. If + "o" is NOT_FOUND, then the contents are not present. */ + + void swap_contents(amorph &other); + //!< Exchanges the contents of "this" and "other". + /*!< No validation is performed but this should always succeed given + amorphs that are constructed properly. */ + +private: + int _fields_used; //!< The number of fields currently full of info. + + void check_fields(const char *where) const; + //!< Crashes out if the field count is wrong. + /*!< This is only used for the debugging version. */ + + void set_nil(int start, int end); + // Puts NIL in the indices between start and end. + /*!< Does not delete whatever used to reside there; it just sets the + pointers to NIL. */ + + // not to be used: amorphs should not be copied because it is intended that + // they support storing heavyweight objects that either have no copy + // constructors or have high-cost copy constructors. + amorph(const amorph &to_copy) {} //!< not to be used. + amorph &operator = (const amorph &to_copy) { return *this; } + //!< not to be used. +}; + +////////////// + +// these extensions are phrased as macros to avoid including the headers +// necessary for compiling them. to use them, just put the macro name in +// the file where the template is needed. + +////////////// + +//! This can be used when the templated object has a copy constructor. +/*! this makes the "to_assign" into a copy of the "to_copy" amorph. */ +template +void amorph_assign(amorph &to_assign, + const amorph &to_copy); + +////////////// + +//! support for packing an amorph into an array of bytes. +/*! + this can be used when the underlying object is based on packable or + supports the same pack/unpack methods. note that if there are empty spots in + the amorph, they are not packed. when the packed form is unpacked again, it will + have only the first N elements set, where N is the number of non-empty items. +*/ +template +void amorph_pack(basis::byte_array &packed_form, const amorph &to_pack); + +//! unpacks the amorph from an array of bytes. +template +bool amorph_unpack(basis::byte_array &packed_form, amorph &to_unpack); + +//! reports how large the packed form will be. +template +int amorph_packed_size(const amorph &to_pack); + +// implementation for longer methods... + +//#define DEBUG_AMORPH + // uncomment to enable more testing, as well as complaints on errors. + +#undef static_class_name +#define static_class_name() "amorph" + // used in bounds_halt macro. + +#undef AMO_ALERT +#ifdef DEBUG_AMORPH + #include + #define AMO_ALERT(a, b, c) basis::throw_error(basis::astring(a), basis::astring(func), basis::astring(b) + " -- " + c) + #define CHECK_FIELDS check_fields(func) +#else + #define AMO_ALERT(a1, a2, a3) {} + #define CHECK_FIELDS { if (!func) {} } +#endif + +////////////// + +template +amorph::amorph(int elements) +: basis::array(elements, NIL, basis::array::SIMPLE_COPY + | basis::array::EXPONE | basis::array::FLUSH_INVISIBLE), + _fields_used(0) +{ + FUNCDEF("constructor"); + set_nil(0, elements - 1); + CHECK_FIELDS; +} + +template +amorph::~amorph() +{ + FUNCDEF("destructor"); + CHECK_FIELDS; + clear_all(); +} + +template +void amorph::set_nil(int start, int end) +{ + for (int i = start; i <= end; i++) + basis::array::put(i, (contents *)NIL); +} + +template +void amorph::check_fields(const char *where) const +{ + FUNCDEF("check_fields"); + int counter = 0; + for (int i = 0; i < elements(); i++) + if (basis::array::get(i)) counter++; + if (_fields_used != counter) { + AMO_ALERT("amorph", basis::a_sprintf("check_fields for %s", where), + "error in _fields_used count"); + } +} + +template +void amorph::reset(int new_maximum) +{ + FUNCDEF("reset"); + CHECK_FIELDS; + adjust(new_maximum); + clear_all(); +} + +template +void amorph::clear_all() +{ + FUNCDEF("clear_all"); + CHECK_FIELDS; + for (int i = 0; i < elements(); i++) clear(i); +} + +template +basis::outcome amorph::append(const contents *data) +{ + FUNCDEF("append"); + CHECK_FIELDS; + adjust(elements() + 1); + return put(elements() - 1, (contents *)data); +} + +template +const contents *amorph::get(int field) const +{ + FUNCDEF("get"); + CHECK_FIELDS; + bounds_return(field, 0, elements() - 1, NIL); + return basis::array::observe()[field]; +} + +template +void amorph::adjust(int new_maximum) +{ + FUNCDEF("adjust"); + CHECK_FIELDS; + if (new_maximum < 0) return; // bad input here. + int old_max = elements(); + if (new_maximum == old_max) return; // nothing to do. + if (new_maximum < old_max) { + // removes the elements beyond the new size of the amorph. + zap(new_maximum, old_max - 1); + // we're done tuning it. + return; + } + + // we get to here if we need more space than we used to. + int new_fields = new_maximum - old_max; + + basis::array::insert(old_max, new_fields); + for (int i = old_max; i < new_maximum; i++) { + basis::array::put(i, NIL); + } +} + +template +basis::outcome amorph::insert(int position, int lines_to_add) +{ + FUNCDEF("insert"); + CHECK_FIELDS; + bounds_return(position, 0, elements(), basis::common::OUT_OF_RANGE); + basis::outcome o = basis::array::insert(position, lines_to_add); + if (o != basis::common::OKAY) return basis::common::OUT_OF_RANGE; + set_nil(position, position + lines_to_add - 1); + return basis::common::OKAY; +} + +template +basis::outcome amorph::zap(int start_index, int end_index) +{ + FUNCDEF("zap"); + CHECK_FIELDS; + bounds_return(start_index, 0, elements() - 1, basis::common::OUT_OF_RANGE); + bounds_return(end_index, 0, elements() - 1, basis::common::OUT_OF_RANGE); + if (end_index < start_index) return basis::common::OKAY; + for (int i = start_index; i <= end_index; i++) clear(i); + basis::outcome o = basis::array::zap(start_index, end_index); + return (o == basis::common::OKAY? basis::common::OKAY : basis::common::OUT_OF_RANGE); +} + +template +basis::outcome amorph::put(int field, const contents *data) +{ + FUNCDEF("put"); + CHECK_FIELDS; + bounds_return(field, 0, elements() - 1, basis::common::OUT_OF_RANGE); + contents *to_whack = acquire(field); + WHACK(to_whack); + if (data) { + basis::array::access()[field] = (contents *)data; + _fields_used++; + } + return basis::common::OKAY; +} + +template +int amorph::find_empty(basis::outcome &o) const +{ + FUNCDEF("find_empty"); + CHECK_FIELDS; + if (_fields_used == elements()) { o = basis::common::IS_FULL; return 0; } + for (int i = 0; i < elements(); i++) + if (!basis::array::get(i)) { o = basis::common::OKAY; return i; } + AMO_ALERT("amorph", "empty", "_fields_used is incorrect"); + return basis::common::IS_FULL; +} + +template +const contents *amorph::next_valid(int &field) const +{ + FUNCDEF("next_valid"); + CHECK_FIELDS; + bounds_return(field, 0, elements() - 1, NIL); + for (int i = field; i < elements(); i++) + if (basis::array::get(i)) { + field = i; + return basis::array::get(i); + } + return NIL; +} + +template +basis::outcome amorph::clear(int field) +{ + FUNCDEF("clear"); + CHECK_FIELDS; + return this->put(field, NIL); +} + +template +contents *amorph::acquire(int field) +{ + FUNCDEF("acquire"); + CHECK_FIELDS; + contents *to_return = borrow(field); + if (to_return) { + _fields_used--; + basis::array::access()[field] = NIL; + } + return to_return; +} + +template +int amorph::find(const contents *to_locate, basis::outcome &o) +{ + FUNCDEF("find"); + CHECK_FIELDS; + if (!_fields_used) { o = basis::common::NOT_FOUND; return 0; } + for (int i = 0; i < elements(); i++) { + if (basis::array::get(i) == to_locate) { + o = basis::common::OKAY; + return i; + } + } + o = basis::common::NOT_FOUND; + return 0; +} + +template +contents *amorph::borrow(int field) +{ + FUNCDEF("borrow"); + CHECK_FIELDS; + bounds_return(field, 0, elements() - 1, NIL); + return basis::array::access()[field]; +} + +template +void amorph::swap_contents(amorph &other) +{ + FUNCDEF("swap_contents"); + CHECK_FIELDS; + int hold_fields = _fields_used; + _fields_used = other._fields_used; + other._fields_used = hold_fields; + this->basis::array::swap_contents(other); +} + +template +int amorph_packed_size(const amorph &to_pack) +{ + int parts_size = 0; + for (int i = 0; i < to_pack.elements(); i++) { + const contents *current = to_pack.get(i); + if (current) parts_size += current->packed_size(); + } + return PACKED_SIZE_INT32 + parts_size; +} + +template +void amorph_pack(basis::byte_array &packed_form, const amorph &to_pack) +{ + structures::attach(packed_form, to_pack.elements()); + for (int i = 0; i < to_pack.elements(); i++) { + const contents *current = to_pack.get(i); + if (current) current->pack(packed_form); + } +} + +template +bool amorph_unpack(basis::byte_array &packed_form, amorph &to_unpack) +{ + to_unpack.reset(); + int elem = 0; + if (!structures::detach(packed_form, elem)) return false; + for (int i = 0; i < elem; i++) { + contents *to_add = new contents; + if (!to_add->unpack(packed_form)) { delete to_add; return false; } + to_unpack.append(to_add); + } + return true; +} + +template +void amorph_assign(amorph &to_assign, + const amorph &to_copy) +{ + if (&to_assign == &to_copy) return; + to_assign.clear_all(); + if (to_assign.elements() != to_copy.elements()) { + to_assign.zap(0, to_assign.elements() - 1); + to_assign.insert(0, to_copy.elements()); + } + for (int i = 0; i < to_assign.elements(); i++) { + if (to_copy.get(i)) to_assign.put(i, new contents(*to_copy.get(i))); + else to_assign.put(i, (contents *)NIL); + } +} + +#undef static_class_name + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/bit_vector.cpp b/nucleus/library/structures/bit_vector.cpp new file mode 100644 index 00000000..8c00528e --- /dev/null +++ b/nucleus/library/structures/bit_vector.cpp @@ -0,0 +1,317 @@ +/*****************************************************************************\ +* * +* Name : bit_vector * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bit_vector.h" + +#include +#include +#include +#include + +//#define DEBUG_BIT_VECTOR + // uncomment this to get debugging noise. + +#undef LOG +#ifdef DEBUG_BIT_VECTOR + #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) +#else + #define LOG(s) {} +#endif + +using namespace basis; + +namespace structures { + +bit_vector::bit_vector() +: _implementation(new byte_array(0, NIL)), _number_of_bits(0) +{} + +bit_vector::bit_vector(int number_of_bits, const abyte *initial) +: _implementation(new byte_array(0, NIL)), _number_of_bits(0) +{ + reset(number_of_bits); + if (!initial) return; + _implementation->reset(number_of_packets(number_of_bits, + int(BITS_PER_BYTE)), initial); +} + +bit_vector::bit_vector(const bit_vector &to_copy) +: _implementation(new byte_array(*to_copy._implementation)), + _number_of_bits(to_copy._number_of_bits) +{} + +bit_vector::~bit_vector() { WHACK(_implementation); } + +bit_vector &bit_vector::operator = (const bit_vector &to_copy) +{ + if (this == &to_copy) return *this; + *_implementation = *to_copy._implementation; + _number_of_bits = to_copy._number_of_bits; + return *this; +} + +bit_vector::operator const byte_array & () const { return *_implementation; } + +int bit_vector::bits() const { return _number_of_bits; } + +bool bit_vector::whole() const { return negative(find_first(0)); } + +bool bit_vector::empty() const { return negative(find_first(1)); } + +bool bit_vector::operator == (const bit_vector &that) const +{ return compare(that, 0, _number_of_bits); } + +bool bit_vector::on(int position) const +{ return get_bit(into_two_dim(position)); } + +bool bit_vector::off(int position) const { return !on(position); } + +void bit_vector::resize(int number_of_bits) +{ +//printf("resize invoked, old size %d, new size %d...\n", bits(), number_of_bits); + if (negative(number_of_bits)) return; + if (bits() == number_of_bits) return; + int old_size = bits(); + _number_of_bits = number_of_bits; + int number_of_bytes = number_of_packets(number_of_bits, int(BITS_PER_BYTE)); + _implementation->resize(number_of_bytes); + // clear new space if the vector got larger. + if (old_size < number_of_bits) { + // lazy reset doesn't compute byte boundary, just does 8 bits. + for (int i = old_size; i < old_size + 8; i++) { + clear(i); +//printf("clearing bit %d.\n", i); + } + // clear the bytes remaining. + int old_bytes = number_of_packets(old_size + 8, int(BITS_PER_BYTE)); + for (int j = old_bytes; j < number_of_bytes; j++) { +//printf("clearing bit %d through %d.\n", j * 8, j * 8 + 7); + _implementation->use(j) = 0; + } + } +} + +void bit_vector::reset(int number_of_bits) +{ + resize(number_of_bits); + memset(_implementation->access(), 0, _implementation->length()); +} + +bit_vector::two_dim_location bit_vector::into_two_dim(int position) const +{ + two_dim_location to_return; + to_return.c_byte = position / BITS_PER_BYTE; + to_return.c_offset = position % BITS_PER_BYTE; + return to_return; +} + +bool bit_vector::get_bit(const two_dim_location &pos_in2) const +{ + bounds_return(pos_in2.c_byte * BITS_PER_BYTE + pos_in2.c_offset, 0, + _number_of_bits - 1, false); + abyte test_mask = abyte(1 << pos_in2.c_offset); + return TEST(abyte(_implementation->get(pos_in2.c_byte)), test_mask); +} + +void bit_vector::set_bit(int position, bool value) +{ + bounds_return(position, 0, bits() - 1, ); + set_bit(into_two_dim(position), value); +} + +void bit_vector::set_bit(const two_dim_location &pos_in2, bool set_it) +{ + abyte test_mask = abyte(1 << pos_in2.c_offset); + if (set_it) SET(_implementation->use(pos_in2.c_byte), test_mask); + else CLEAR((abyte &)_implementation->get(pos_in2.c_byte), test_mask); +} + +bool bit_vector::operator [](int position) const +{ + bounds_return(position, 0, _number_of_bits - 1, false); + return get_bit(into_two_dim(position)); +} + +void bit_vector::light(int position) +{ + bounds_return(position, 0, _number_of_bits - 1, ); + set_bit(into_two_dim(position), true); +} + +void bit_vector::clear(int position) +{ + bounds_return(position, 0, _number_of_bits - 1, ); + set_bit(into_two_dim(position), false); +} + +int bit_vector::find_first(bool to_find) const +{ + const abyte whole_set = abyte(0xFF); + // search through the whole bytes first. + for (int full_byte = 0; full_byte < _implementation->length(); full_byte++) { + if ( (to_find && _implementation->get(full_byte)) + || (!to_find && (_implementation->get(full_byte) != whole_set)) ) { + // the first appropriate byte is searched for the first appropriate bit. + for (int i = full_byte * BITS_PER_BYTE; i < minimum + (int(_number_of_bits), (full_byte+1)*BITS_PER_BYTE); i++) { + if (on(i) == to_find) return i; + } + return common::NOT_FOUND; + } + } + return common::NOT_FOUND; +} + +bool bit_vector::compare(const bit_vector &that, int start, int stop) const +{ + for (int i = start; i <= stop; i++) { + if (on(i) != that.on(i)) return false; + } + return true; +} + +astring bit_vector::text_form() const +{ + astring to_return; + int bits_on_line = 0; + const int estimated_elements_on_line = 64; + for (int i = 0; i < _number_of_bits; i++) { + // below prints out the bit number heading. + if (bits_on_line == 0) { + if (i != 0) to_return += "\n"; + if (i < 10000) to_return += " "; + if (i < 1000) to_return += " "; + if (i < 100) to_return += " "; + if (i < 10) to_return += " "; + to_return += a_sprintf("%d", i); + to_return += " | "; + } + if (on(i)) to_return += "1"; + else to_return += "0"; + bits_on_line++; + if (bits_on_line >= estimated_elements_on_line) bits_on_line = 0; + else if ( !(bits_on_line % BITS_PER_BYTE) ) to_return += " "; + } + to_return += "\n"; + return to_return; +} + +bit_vector bit_vector::subvector(int start, int end) const +{ + bounds_return(start, 0, bits() - 1, bit_vector()); + bounds_return(end, 0, bits() - 1, bit_vector()); + int size = end - start + 1; + bit_vector to_return(size); + for (int i = start; i <= end; i++) to_return.set_bit(i - start, on(i)); + return to_return; +} + +bool bit_vector::overwrite(int start, const bit_vector &to_write) +{ + bounds_return(start, 0, bits() - 1, false); + int end = start + to_write.bits() - 1; + bounds_return(end, 0, bits() - 1, false); + for (int i = start; i <= end; i++) set_bit(i, to_write[i - start]); + return true; +} + +enum endian { LEFT_ENDIAN, RIGHT_ENDIAN }; +endian host_byte_order = LEFT_ENDIAN; // bytes within words. +endian host_bit_order = LEFT_ENDIAN; // bits within bytes. + +// probably the treatment for right endian in either case +// of bytes or bits is wrong. + +bool bit_vector::set(int start, int size, basis::un_int source) +{ + bounds_return(start, 0, bits() - 1, false); + int end = start + size - 1; + bounds_return(end, 0, bits() - 1, false); + bounds_return(size, 1, 32, false); + bit_vector from_int(32, (abyte *)&source); + +// is this algorithm even remotely near the truth? + if (host_bit_order == RIGHT_ENDIAN) + from_int._implementation->resize(size, byte_array::NEW_AT_BEGINNING); + else from_int.resize(size); // left endian machine. + overwrite(start, from_int); + return true; +} + +basis::un_int bit_vector::get(int start, int size) const +{ + int end = start + size - 1; + bit_vector segment = subvector(start, end); + // padding to bytes. + int new_length = segment.bits(); + if (new_length % 8) { + new_length = ( (new_length+8) / 8) * 8; + LOG(a_sprintf("new size is %d.", new_length)); + } + segment.resize(new_length); + + if (host_bit_order == RIGHT_ENDIAN) { + bit_vector new_segment(segment.bits()); + for (int i = 0; i < segment.bits(); i += 8) + for (int j = i; j < i + BITS_PER_BYTE; j++) + if (j < segment.bits()) + new_segment.set_bit(i + (7 - (j - i)), segment[j]); + segment = new_segment; // copy the new form. + } + + LOG("new seg after bit copy:"); + LOG(segment); + + basis::un_int to_return = 0; + + int bytes_used = number_of_packets(segment.bits(), int(BITS_PER_BYTE)); + // 4 = # of bytes in a int. + for (int i = minimum(4, bytes_used) - 1; i >= 0; i--) { +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp(8, &segment._implementation->get(i)); + LOG(a_sprintf("%d: src bits %s", i, tmp.text_form().s())); +#endif + +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp4(32, (abyte *)&to_return); + LOG(a_sprintf("%d: pre shift dest %s", i, tmp4.text_form().s())); +#endif + if (host_byte_order == LEFT_ENDIAN) to_return <<= 8; + else to_return >>= 8; +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp5(32, (abyte *)&to_return); + LOG(a_sprintf("%d: post shift dest %s", i, tmp5.text_form().s())); +#endif + + basis::un_int mask = segment._implementation->get(i); + if (host_byte_order == RIGHT_ENDIAN) mask <<= 23; +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp3(32, (abyte *)&to_return); + LOG(a_sprintf("%d: pre dest bits %s", i, tmp3.text_form().s())); +#endif + SET(to_return, mask); +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp2(32, (abyte *)&to_return); + LOG(a_sprintf("%d: post dest bits %s", i, tmp2.text_form().s())); +#endif + } + +#ifdef DEBUG_BIT_VECTOR + bit_vector tmp(32, (abyte *)&to_return); + LOG(a_sprintf("final bits %s", tmp.text_form().s())); +#endif + return to_return; +} + +} //namespace. diff --git a/nucleus/library/structures/bit_vector.h b/nucleus/library/structures/bit_vector.h new file mode 100644 index 00000000..55abae95 --- /dev/null +++ b/nucleus/library/structures/bit_vector.h @@ -0,0 +1,162 @@ +#ifndef BIT_VECTOR_CLASS +#define BIT_VECTOR_CLASS + +/*****************************************************************************\ +* * +* Name : bit_vector * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace structures { + +//! An array of bits with operations for manipulating and querying individual bits. + +class bit_vector +{ +public: + bit_vector(); + //!< creates a zero length bit_vector. + + bit_vector(int size, const basis::abyte *initial = NIL); + //!< creates a bit_vector able to store "size" bits. + /*!< if initial is NIL, the vector is initialized to zero. otherwise, the + bits are copied from "initial". "initial" must be large enough for the + copying to succeed. */ + + bit_vector(const bit_vector &to_copy); + + ~bit_vector(); + + bit_vector &operator =(const bit_vector &to_copy); + + int bits() const; + //!< returns the number of bits in the vector. + + bool operator [] (int position) const; + //!< returns the value of the bit at the position specified. + + bool on(int position) const; + //!< returns true if the bit at "position" is set. + + bool off(int position) const; + //!< returns true if the bit at "position" is clear. + + void set_bit(int position, bool value); + //!< sets the bit at "position" to a particular "value". + + bool whole() const; + //!< returns true if entire vector is set. + + bool empty() const; + //!< returns true if entire vector is unset. + + int find_first(bool to_find) const; + //!< Seeks the first occurrence of "to_find". + /*!< locates the position at which a bit equal to to_find is located or it + returns common::NOT_FOUND if no bit of that value is in the vector. */ + + void light(int position); + //!< sets the value of the bit at "position". + + void clear(int position); + //!< clears the value of the bit at "position". + + void resize(int size); + //!< Changes the size of the bit_vector to "size" bits. + /*!< This keeps any bits that still fit. Any new bits are set to zero. */ + + void reset(int size); + //!< resizes the bit_vector and clears all bits in it. + + bool compare(const bit_vector &that, int start, int stop) const; + //!< true if "this" is the same as "that" between "start" and "stop". + + bool operator == (const bit_vector &that) const; + //!< returns true if "this" is equal to "that". + /*!< neither vector is changed. if the vectors do not have the same size, + false is returned. */ + + basis::astring text_form() const; + //!< prints a nicely formatted list of bits to a string. + + bit_vector subvector(int start, int end) const; + //!< Extracts a chunk of the vector between "start" and "end". + /*!< Returns a bit_vector that is copied from this one starting at "start" + and running until "end". An empty bit vector is returned if the indices + are out of range. */ + + bool overwrite(int start, const bit_vector &to_write); + //!< Stores bits from "to_write" into "this" at "start". + /*!< overwrites the contents of this bit_vector with the contents of + "to_write", beginning at "start" in this bit_vector. true is returned + for a successful write. false will be returned if the "start" is out of + range or if "to_write" is too large. */ + + basis::un_int get(int start, int size) const; + //!< gets a portion of the vector as an unsigned integer. + /*!< returns an integer (as interpreted by the operating system) where the + pattern of bits in that integer is identical to the bits in this + bit_vector, beginning at "start" and including enough bits for an + integer of "size" bits. */ + + bool set(int start, int size, basis::un_int source); + //!< puts the "source" value into the vector at "start". + /*!< sets the pattern of bits in this bit_vector beginning at "start" + identically to how they are stored in the integer "source", where the + integer is expected to be using "size" of its bits. the bits are copied + starting from the low end of "source", where the operating system + defines what the low end is. true is returned for a successful copying. */ + + operator const basis::byte_array &() const; + //!< returns a copy of the low-level implementation of the bit vector. + /*!< the first bit is stored at the bit in first byte, and so forth. */ + +private: + basis::byte_array *_implementation; //!< holds the real state of the bits. + int _number_of_bits; //!< the total number of bits possible in this vector. + + struct two_dim_location { int c_byte; int c_offset; }; + //!< a two-dimensional position given by byte number and offset within byte. + + two_dim_location into_two_dim(int position) const; + /*!< turns a bit position in the vector into a two dimensional position + of the byte number and a bit offset within that byte. */ + bool get_bit(const two_dim_location &pos_in2) const; + //!< returns the value of the bit given its two dimensional location. + void set_bit(const two_dim_location &pos_in2, bool value); + //!< sets the value of the bit if "value", and clears it if not. +}; + +////////////// + +// NOTE: these are operations on numbers, NOT on bit_vectors. + +//! returns a number based on "to_modify" but with "bits" turned on. +template +void SET(type &to_modify, type bits) { to_modify |= bits; } + +//! returns a number based on "to_modify" but with "bits" turned off. +template +void CLEAR(type &to_modify, type bits) { to_modify &= (type)~bits; } + +//! returns non-zero if the "bits" bit pattern is turned on in "to_test". +template +bool TEST(type to_test, type bits) { return bool(!(!(to_test & bits))); } + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/byte_hasher.h b/nucleus/library/structures/byte_hasher.h new file mode 100644 index 00000000..f7631259 --- /dev/null +++ b/nucleus/library/structures/byte_hasher.h @@ -0,0 +1,56 @@ +#ifndef ROTATING_BYTE_HASHER_CLASS +#define ROTATING_BYTE_HASHER_CLASS + +/*****************************************************************************\ +* * +* Name : rotating_byte_hasher * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_hasher.h" +#include "checksums.h" +#include "hash_table.h" + +#include + +namespace structures { + +//! Implements a hashing algorithm based on the contents stored in an object. +/*! + This will only be usable for key types that have flat members; keys with + pointers limit the meaning of the hash value, or destroy the meaning if the + pointer value can change between lookups. Note that objects based on RTTI + will probably never work with this either since the compiler stores extra + data as part of the binary form for those objects. +*/ + +class rotating_byte_hasher : public virtual hashing_algorithm +{ +public: + virtual ~rotating_byte_hasher() {} + + virtual basis::un_int hash(const void *key_data, int key_length) const + { return checksums::hash_bytes(key_data, key_length); } + //!< returns a value that can be used for indexing into a hash table. + /*!< the returned value is loosely based on the "key_data" and the + "key_length" we are provided with. note: do not use a huge key length + for this or your hash table will be very slow; the key should probably + be limited to 16 or less. */ + + virtual hashing_algorithm *clone() const + { return new rotating_byte_hasher; } + //!< implements cloning of the algorithm object. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/checksums.cpp b/nucleus/library/structures/checksums.cpp new file mode 100644 index 00000000..16be2f1f --- /dev/null +++ b/nucleus/library/structures/checksums.cpp @@ -0,0 +1,98 @@ +/*****************************************************************************\ +* * +* Name : checksums group * +* Authors: Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "checksums.h" + +#include + +using namespace basis; + +namespace structures { + +const int HIGHEST_MOD_VALUE = 32014; + +unsigned int checksums::bizarre_checksum(const abyte *data, int length) +{ + int sum = 0; + for (int i = 0; i < length; i++) sum += data[i] % 23 + i % 9; + sum = (sum % (HIGHEST_MOD_VALUE - 1)) + 1; + return (unsigned int)sum; +} + +// fletcher checksum is from Dr. Dobbs Journal May 1992, pp. 32-38. + +basis::un_short checksums::fletcher_checksum(const abyte *data, int length) +{ + int sum1 = 0; + basis::un_int sum2 = 0; + const abyte *buffer = data; + + while (length--) { + sum1 += int(*buffer++); + // check for overflow into high byte. + if (sum1 > 255) sum1++; + sum1 &= 0xFF; // remove any bits in high byte for next sum. + sum2 += basis::un_int(sum1); + } + if (sum1 == 255) sum1 = 0; + unsigned int fletch = basis::un_int(sum2 & 0xFF); + fletch <<= 8; + fletch |= basis::un_int(sum1 & 0xFF); + + return basis::un_short(fletch); +} + +basis::un_short checksums::rolling_fletcher_checksum(basis::un_short previous, const abyte *data, + int len) +{ return previous ^ fletcher_checksum(data, len); } + +abyte checksums::byte_checksum(const abyte *data, int length) +{ + abyte to_return = 0; + for (int i = 0; i < length; i++) to_return += abyte(data[i]); + return to_return; +} + +basis::un_int checksums::short_checksum(const abyte *data, int length) +{ + basis::un_int to_return = 0; + for (int i = 0; i < length; i++) to_return += data[i]; + return to_return; +} + +basis::un_int checksums::hash_bytes(const void *key_data, int key_length) +{ + if (!key_data) return 0; // error! + if (!key_length) return 0; // ditto! + + abyte *our_key = (abyte *)key_data; + abyte hashed[4] = { 0, 0, 0, 0 }; + + int fill_posn = 0; + for (int i = 0; i < key_length; i++) { + // add to the primary area. + hashed[fill_posn] = hashed[fill_posn] + our_key[i]; + fill_posn++; + if (fill_posn >= 4) fill_posn = 0; + // add to the secondary area (the next in rotation after primary). + hashed[fill_posn] = hashed[fill_posn] + (our_key[i] / 4); + } + + basis::un_int to_return = 0; + for (int j = 0; j < 4; j++) to_return = (to_return << 8) + hashed[j]; + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/structures/checksums.h b/nucleus/library/structures/checksums.h new file mode 100644 index 00000000..4f3f0ba8 --- /dev/null +++ b/nucleus/library/structures/checksums.h @@ -0,0 +1,61 @@ +#ifndef CHECKSUMS_GROUP +#define CHECKSUMS_GROUP + +/*****************************************************************************\ +* * +* Name : checksums group * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +/* @file checksums.h + A collection of useful checksum calculators. +*/ + +namespace structures { + +class checksums +{ +public: + + static basis::abyte byte_checksum(const basis::abyte *data, int length); + //!< simple byte-sized checksum based on additive roll-over. + + static basis::un_int short_checksum(const basis::abyte *data, int length); + //!< simple shorty checksum based on additive roll-over. + + static basis::un_short fletcher_checksum(const basis::abyte *data, int length); + //!< A positionally computed error detection value. + + static basis::un_short rolling_fletcher_checksum(basis::un_short previous, const basis::abyte *data, int len); + //!< Fletcher checksums applied to streaming data. + /*!< this is not strictly a fletcher checksum, but it uses the normal + fletcher checksum on the specified data and XORs it with the "previous" + value of the checksum. this leads to a regenerable number that should + always be the same if done on the same data using the same chunking + factor (the "len"), although of course the last piece of data does not + have to be "len" bytes. */ + + static unsigned int bizarre_checksum(const basis::abyte *data, int length); + //!< A different type of checksum with somewhat unknown properties. + /*!< It attempts to be incorporate positioning of the bytes. */ + + static basis::un_int hash_bytes(const void *key_data, int key_length); + //!< returns a value that can be used for indexing into a hash table. + /*!< the returned value is loosely based on the "key_data" and the + "key_length" we are provided with. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/hash_table.h b/nucleus/library/structures/hash_table.h new file mode 100644 index 00000000..4eb02856 --- /dev/null +++ b/nucleus/library/structures/hash_table.h @@ -0,0 +1,597 @@ +#ifndef HASH_TABLE_CLASS +#define HASH_TABLE_CLASS + +/*****************************************************************************\ +* * +* Name : hash_table * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "amorph.h" + +#include +#include +#include +#include +#include + +#include + +namespace structures { + +// forward. +template class internal_hash_array; + +//! A hashing algorithm takes a key and derives a related integer from it. +/*! + The hashing_algorithm is used in hash_tables for placing objects into slots + that can be easily found again. The numerical hash should be faster than + actually doing a search on what might be unsorted data otherwise. +*/ + +class hashing_algorithm : public virtual basis::clonable +{ +public: + virtual basis::un_int hash(const void *key_data, int key_length) const = 0; + //!< returns a hash value based on the "key_data" and "key_length". + /*!< the "key_length" is provided from the sizeof() of the key type used + in the hash_table (below). it is up to the implementor to create a hash + value that spreads the keys to be hashed appropriately. if similar keys + create same or similar hash values, then the efficiency of the hash_table + is compromised. */ + + virtual hashing_algorithm *clone() const = 0; + //!< supports virtual construction of new algorithm objects. +}; + +////////////// + +//! Implements hashing into buckets for quick object access. +/*! + The buckets are currently simple lists, but if the hashing algorithm is well + chosen, then that's not a major problem. This makes lookups a lot faster + than a linear search, but no particular performance guarantees are made at + this time (hmmm). +*/ + +template + // the key_type must support valid copy constructor, assignment operator and + // equality operators. the contents need not support any special operations; + // it is always considered as just a pointer. +class hash_table : public virtual basis::nameable +{ +public: + hash_table(const hashing_algorithm &hasher, int estimated_elements); + //!< Creates a table using the "hasher" that is ready to store "estimated_elements". + /*!< the "hasher" must provide the hashing algorithm for the two types + specified. if the implementation is thread safe, then one object can + be constructed to use with all the same types of hash tables. + note that "hasher" must exist longer than any classes based on it; do + not let "hasher" go out of scope or the hash table will explode. */ + + virtual ~hash_table(); + //!< destroys any objects left in the hash_table. + + DEFINE_CLASS_NAME("hash_table"); + + void rehash(int estimated_elements); + //!< resizes the hashing structures to optimise for a new size of "estimated_elements". + /*!< this is a potentially expensive operation. */ + + enum outcomes { + IS_NEW = basis::common::IS_NEW, + EXISTING = basis::common::EXISTING + }; + + static int calculate_num_slots(int estimated_elements); + //!< a helper method that lets us know what n is for how many 2^n slots we should have. + + int elements() const; + //!< the number of valid items we found by traversing the hash table. + /*!< this is a very costly method. */ + + int estimated_elements() const { return c_estim_elements; } + //!< returns the size of table we're optimized for. + + basis::outcome add(const key_type &key, contents *to_store); + //!< Stores "to_store" into the table given its "key" for hashing. + /*!< This places an entry in the hash table with the contents "to_store", + using the "key" structure to identify it. the return value reports whether + the "key" was already in the list or not. if the "key" was already in use, + then the contents for it get replaced with "to_store". note that the hash + table assumes ownership of the "to_store" object but just makes a copy of + the key. thus, if an item is replaced, the previous contents are + destroyed. */ + + basis::outcome fast_dangerous_add(const key_type &key, contents *to_store); + //!< Like the add method above, but doesn't check for duplicates. + /*!< This should only ever be used when one has already guaranteed that + the table doesn't have a duplicate item for the "key" specified. */ + + bool find(const key_type &key, contents * &item_found) const; + //!< locates the item specified by the "key", if possible. + /*!< true is returned on success and the "item_found" is filled in. the + "item_found" is a pointer to the actual data held in the table, so do not + destroy or otherwise damage the "item_found". */ + + contents *find(const key_type &key) const + { contents *c = NIL; return find(key, c)? c : NIL; } + //!< simplified form of above find() method. + + contents *acquire(const key_type &key); + //!< retrieves the contents held for "key" out of the table. + /*!< afterwards, the contents pointer is the caller's responsibility; it + is no longer in the table and must be destroyed externally. if no item + was found for the "key", then NIL is returned. */ + + bool zap(const key_type &key); + //!< removes the entry with the "key" specified. + /*!< true is returned if the item was found and destroyed. */ + + void reset(); + //!< removes all entries in the table and returns it to a pristine state. + + typedef bool apply_function(const key_type &key, contents ¤t, + void *data_link); + //!< the "apply_function" is what a user of the "apply" method must supply. + /*!< the function will be called on every item in the table unless one of + the invocations returns false; this causes the apply process to stop. + the "data_link" provides a way for the function to refer back to an + parent class of some sort. */ + + void apply(apply_function *to_apply, void *data_link); + //!< Invokes the function "to_apply" on every entry in the table. + /*!< This calls the "to_apply" function on every item in the catalog + until the function returns false. The "data_link" pointer is available to + the applied method and can be used to communicate an object for use during + the iteration. NOTE: it is NOT safe to rearrange or manipulate the table + in any way from your "to_apply" function. */ + + basis::outcome add(key_type *key, contents *to_store, bool check_dupes = true); + //!< specialized add for a pre-existing pointer "key". + /*!< responsibility for the "key" is taken over by the hash table, as of + course it is for the "to_store" pointer. this just allows others to + generate new keys and hand them over to us when it might otherwise be + very costly to copy the key structure. if "check_dupes" is not true, + then that asserts that you have independently verified that there's no + need to check whether the key is already present. */ + + bool verify() const; + //!< returns true if the hash table is internally consistent. + /*!< this is mainly for debugging. */ + +private: + int c_estim_elements; //!< expected running size for the hash table. + hashing_algorithm *_hasher; //!< algorithm for getting hash value. + internal_hash_array *_table; //!< storage area. + int _last_iter; + //!< tracks where we left off iterating. we restart just after that spot. + + hash_table(const hash_table &to_copy); + //!< not allowed; use the copy_hash_table function below. + hash_table &operator =(const hash_table &to_copy); + //!< not allowed; use the copy_hash_table function below. + +public: + internal_hash_array &table_access() const; + //!< special accessor for the copy_hash_table method only. +}; + +////////////// + +//! Copies the entire hash table, given a contents type that supports copying. +/*! + Provides a copy operation on hash tables where the contents object supports + a copy constructor, which is not appropriate to require in general. The + hash_table held in "target" will be wiped out and replaced with items + equivalent to those in "source". */ + +template +void copy_hash_table(hash_table &target, + const hash_table &source); + +////////////// + +// implementations for longer methods below.... + +// this is a very special micro-managed class. it holds two pointers which +// it neither creates nor destroys. thus all interaction with it must be +// careful about removing these objects at the appropriate time. we don't +// want automatic memory management since we want a cheap copy on the items +// in the buckets. + +template +class hash_wrapper : public virtual basis::root_object +{ +public: + key_type *_id; + contents *_data; + + hash_wrapper(key_type *id = NIL, contents *data = NIL) + : _id(id), _data(data) {} +}; + +////////////// + +template +class bucket + : public basis::array >, + public virtual basis::root_object +{ +public: + bucket() : basis::array >(0, NIL, + basis::byte_array::SIMPLE_COPY | basis::byte_array::EXPONE + | basis::byte_array::FLUSH_INVISIBLE) {} + + int find(const key_type &to_find) { + for (int i = 0; i < this->length(); i++) { + key_type *curr_key = this->get(i)._id; +//hmmm: if curr key is not set, is that a logic error? it seems like we +// are seeing the potential for this. + if (curr_key && (to_find == *curr_key)) + return i; + } + return basis::common::NOT_FOUND; + } +}; + +////////////// + +template +class internal_hash_array : public amorph > +{ +public: + internal_hash_array(int estimated_elements) + : amorph > + (hash_table::calculate_num_slots(estimated_elements)) {} +}; + +////////////// + +template +hash_table::hash_table(const hashing_algorithm &hasher, int estimated_elements) +: c_estim_elements(estimated_elements), + _hasher(hasher.clone()), + _table(new internal_hash_array(estimated_elements)), + _last_iter(0) +{} + +template +hash_table::~hash_table() +{ +#ifdef EXTREME_CHECKING + FUNCDEF("destructor"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + reset(); + basis::WHACK(_table); + basis::WHACK(_hasher); +} + +template +int hash_table::calculate_num_slots(int estimated_elements) +{ +//printf("elems wanted = %d\n", estimated_elements); + int log_2_truncated = int(log(float(estimated_elements)) / log(2.0)); +//printf("log 2 of elems, truncated = %d\n", log_2_truncated); + int slots_needed_for_elems = (int)pow(2.0, double(log_2_truncated + 1)); +//printf("slots needed = %d\n", slots_needed_for_elems ); + return slots_needed_for_elems; +} + +// the specialized copy operation. +template +void copy_hash_table(hash_table &target, + const hash_table &source) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("copy_hash_table"); + if (!source.verify()) + deadly_error(class_name(), func, "source state did not verify."); +#endif + target.reset(); + for (int i = 0; i < source.table_access().elements(); i++) { + bucket *buck = source.table_access().borrow(i); + if (!buck) continue; + for (int j = 0; j < buck->length(); j++) { + target.add(*((*buck)[j]._id), new contents(*((*buck)[j]._data))); + } + } +#ifdef EXTREME_CHECKING + if (!target.verify()) + deadly_error(class_name(), func, "target state did not verify."); +#endif + #undef class_name +} + +template +void hash_table::reset() +{ +#ifdef EXTREME_CHECKING + FUNCDEF("reset"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + // iterate over the list whacking the content items in the buckets. + for (int i = 0; i < _table->elements(); i++) { + bucket *buck = _table->acquire(i); + if (!buck) continue; + for (int j = 0; j < buck->length(); j++) { + basis::WHACK((*buck)[j]._data); // eliminate the stored data. + basis::WHACK((*buck)[j]._id); // eliminate the stored key. + buck->zap(j, j); // remove the element. + j--; // skip back before whack happened. + } + basis::WHACK(buck); + } +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif +} + +template +bool hash_table::verify() const +{ + for (int i = 0; i < _table->elements(); i++) { + const bucket *buck = _table->borrow(i); + if (!buck) continue; // that's acceptable. + for (int j = 0; j < buck->length(); j++) { + const hash_wrapper &wrap = (*buck)[j]; + if (!wrap._data) { +// program_wide_logger::get().log(a_sprintf("hash table: no data segment at position %d.", j)); + return false; + } + if (!wrap._id) { +// program_wide_logger::get().log(a_sprintf("hash table: no identifier at position %d.", j)); + return false; + } + } + } + return true; +} + +template +internal_hash_array &hash_table + ::table_access() const +{ return *_table; } + +template +void hash_table::rehash(int estimated_elements) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("rehash"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + typedef bucket buckie; + hash_table new_hash(*_hasher, estimated_elements); + // this is the new table that will be used. + + // scoot through the existing table so we can move items into the new one. + for (int i = 0; i < _table->elements(); i++) { + buckie *b = _table->borrow(i); // look at the current element. + if (!b) continue; // nothing here, keep going. + for (int j = b->length() - 1; j >= 0; j--) { + key_type *k = b->use(j)._id; + contents *c = b->use(j)._data; + new_hash.add(k, c); + } + b->reset(); + // clean out any items in the buckets here now that they've moved. + } + + // now flip the contents of the new guy into "this". + _table->reset(); // toss previous stuff. + _table->adjust(new_hash._table->elements()); + for (int q = 0; q < new_hash._table->elements(); q++) + _table->put(q, new_hash._table->acquire(q)); + // reset other data members. + c_estim_elements = new_hash.c_estim_elements; + _last_iter = 0; +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif +} + +template +basis::outcome hash_table::add(const key_type &key, + contents *to_store) +{ return add(new key_type(key), to_store); } + +template +basis::outcome hash_table::add(key_type *key, + contents *to_store, bool check_dupes) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("add"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + // get a hash value. + basis::un_int hashed = _hasher->hash((const void *)key, sizeof(key_type)); + // make the value appropriate for our table. + hashed = hashed % _table->elements(); + // see if the key already exists there. + bucket *buck = _table->borrow(hashed); + if (!buck) { + // this slot doesn't exist yet. + buck = new bucket; + _table->put(hashed, buck); + } + if (!check_dupes) { + // we aren't even going to check for its existence. + *buck += hash_wrapper(key, to_store); + return IS_NEW; + } + int indy = buck->find(*key); + if (basis::negative(indy)) { + // that value was not seen yet, so we're adding a new entry. + *buck += hash_wrapper(key, to_store); +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif + return IS_NEW; + } + // that key already existed, so we'll re-use its slot with the new data. + basis::WHACK((*buck)[indy]._data); + basis::WHACK(key); // toss since we're not using. + (*buck)[indy]._data = to_store; +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif + return EXISTING; +} + +template +basis::outcome hash_table::fast_dangerous_add + (const key_type &key, contents *to_store) +{ return add(new key_type(key), to_store, false); } + +template +bool hash_table::find(const key_type &key, + contents * &item_found) const +{ +#ifdef EXTREME_CHECKING + FUNCDEF("find"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + item_found = NIL; + // get a hash value. + basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); + // make the value appropriate for our table. + hashed = hashed % _table->elements(); + // see if the key exists in the bucket. + bucket *buck = _table->borrow(hashed); + if (!buck) return false; + int indy = buck->find(key); + if (basis::negative(indy)) return false; // not there. + // was there, so retrieve the data. + item_found = (*buck)[indy]._data; + return true; +} + +template +contents *hash_table::acquire(const key_type &key) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("acquire"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + // get a hash value. + basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); + // make the value appropriate for our table. + hashed = hashed % _table->elements(); + // see if the key exists in the bucket. + bucket *buck = _table->borrow(hashed); + if (!buck) return NIL; + int indy = buck->find(key); + if (basis::negative(indy)) return NIL; // nope, not there. + contents *to_return = (*buck)[indy]._data; + basis::WHACK((*buck)[indy]._id); // clean the id. + buck->zap(indy, indy); // toss the storage blob for the item. +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif + return to_return; +} + +template +bool hash_table::zap(const key_type &key) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("zap"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + // get a hash value. + basis::un_int hashed = _hasher->hash((const void *)&key, sizeof(key)); + // make the value appropriate for our table. + hashed = hashed % _table->elements(); + // see if the key exists in the bucket. + bucket *buck = _table->borrow(hashed); + if (!buck) return false; + int indy = buck->find(key); + if (basis::negative(indy)) { + // nope, not there. + return false; + } + basis::WHACK((*buck)[indy]._data); // delete the data held. + basis::WHACK((*buck)[indy]._id); // delete the data held. + buck->zap(indy, indy); // toss the storage blob for the item. + if (!buck->length()) { + // clean up this bucket now. + buck = _table->acquire(hashed); + basis::WHACK(buck); + } +#ifdef EXTREME_CHECKING + if (!verify()) + deadly_error(class_name(), func, "state did not verify afterwards."); +#endif + return true; +} + +template +void hash_table::apply(apply_function *to_apply, + void *data_link) +{ +#ifdef EXTREME_CHECKING + FUNCDEF("apply"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + typedef bucket buckie; + int bucks_seen = 0; + int posn = _last_iter; // start at the same place we left. + while (bucks_seen++ < _table->elements()) { + if ( (posn < 0) || (posn >= _table->elements()) ) + posn = 0; + buckie *b = _table->borrow(posn); + _last_iter = posn++; + // record where the iteration last touched and increment next position. + // we must do this before we check if the bucket exists or we'll rotate + // on this same place forever. + if (!b) continue; // nothing here, keep going. + for (int j = 0; j < b->length(); j++) { + contents *c = b->use(j)._data; + if (!c) { +#ifdef EXTREME_CHECKING + deadly_error("hash_table", "apply", "logic error--missing pointer"); +#endif + continue; + } + if (!to_apply(*b->use(j)._id, *c, data_link)) return; + } + } +} + +template +int hash_table::elements() const +{ +#ifdef EXTREME_CHECKING + FUNCDEF("elements"); + if (!verify()) deadly_error(class_name(), func, "state did not verify."); +#endif + typedef bucket buckie; + int to_return = 0; + for (int i = 0; i < _table->elements(); i++) { + bucket *buck = _table->borrow(i); + if (!buck) continue; // nothing to count. + to_return += buck->length(); + } + return to_return; +} + +#undef static_class_name + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/structures/int_hash.h b/nucleus/library/structures/int_hash.h new file mode 100644 index 00000000..bd5f3494 --- /dev/null +++ b/nucleus/library/structures/int_hash.h @@ -0,0 +1,132 @@ +#ifndef INT_HASH_CLASS +#define INT_HASH_CLASS + +/*****************************************************************************\ +* * +* Name : int_hash * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_hasher.h" +#include "hash_table.h" + +#include +#include + +namespace structures { + +//! A hash table for storing integers. +/*! + Implements a hash table indexed on integers that maintains a separate set of + identifiers for listing the items that are presently in the hash table. This + slows down additions somewhat, but finds are not affected. The advantage of + the separate index is that the apply() method is much faster. +*/ + +template +class int_hash : public hash_table +{ +public: + int_hash(int max_bits); + ~int_hash(); + + const int_set &ids() const; + void ids(int_set &ids) const; + //!< provides the current list of valid identifiers. + + basis::outcome add(int key, contents *to_store); + //!< overrides base add() and ensures that the id list stays up to date. + contents *acquire(int key); + //!< overrides base acquire() by ensuring that the ids stay up to date. + bool zap(int key); + //!< overrides base zap() method plus keeps id list updated. + void reset(); + //!< overrides base reset() and ensures that the id list stays up to date. + + typedef bool apply_function(const int &key, contents ¤t, + void *data_link); + + void apply(apply_function *to_apply, void *data_link); + //!< operates on every item in the int_hash table. + +private: + int_set *_ids; + //!< a separate list of the identifiers stored here. + /*! this provides a fairly quick way to iterate rather than having to span + the whole hash table. it does slow down zap() a bit though. */ +}; + +////////////// + +// implementations below... + +template +int_hash::int_hash(int max_bits) +: hash_table(rotating_byte_hasher(), max_bits), + _ids(new int_set) +{} + +template +int_hash::~int_hash() +{ WHACK(_ids); } + +template +const int_set &int_hash::ids() const { return *_ids; } + +template +void int_hash::ids(int_set &ids) const { ids = *_ids; } + +template +basis::outcome int_hash::add(int key, contents *to_store) +{ + _ids->add(key); + return hash_table::add(key, to_store); +} + +template +contents *int_hash::acquire(int key) +{ + _ids->remove(key); + return hash_table::acquire(key); +} + +template +bool int_hash::zap(int key) +{ + _ids->remove(key); + return hash_table::zap(key); +} + +template +void int_hash::reset() +{ + _ids->clear(); + hash_table::reset(); +} + +template +void int_hash::apply(apply_function *to_apply, void *data_link) +{ + for (int i = 0; i < _ids->elements(); i++) { + int current = (*_ids)[i]; + contents *found = hash_table::find(current); + if (!found) { + _ids->remove(current); + continue; + } + to_apply(current, *found, data_link); + } +} + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/structures/makefile b/nucleus/library/structures/makefile new file mode 100644 index 00000000..78155565 --- /dev/null +++ b/nucleus/library/structures/makefile @@ -0,0 +1,10 @@ +include cpp/variables.def + +PROJECT = structures +TYPE = library +SOURCE = bit_vector.cpp checksums.cpp memory_limiter.cpp object_packers.cpp \ + static_memory_gremlin.cpp string_hasher.cpp string_table.cpp version_record.cpp +TARGETS = structures.lib + +include cpp/rules.def + diff --git a/nucleus/library/structures/matrix.h b/nucleus/library/structures/matrix.h new file mode 100644 index 00000000..96ec3e07 --- /dev/null +++ b/nucleus/library/structures/matrix.h @@ -0,0 +1,352 @@ +#ifndef MATRIX_CLASS +#define MATRIX_CLASS + +/*****************************************************************************\ +* * +* Name : matrix * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +namespace structures { + +//! Represents a two-dimensional array of objects. +/*! + The indices range from zero to (maximum_value - 1) for rows and columns. +*/ + +template +class matrix : protected basis::array +{ +public: + matrix(int rows = 0, int cols = 0, contents *data = NIL); + //!< the "data" array must have at least "rows" * "cols" contents in it. + matrix(const matrix &to_copy); + + ~matrix() {} + + int rows() const { return _rows; } + int columns() const { return _cols; } + + matrix &operator = (const matrix &to_copy); + + contents &get(int row, int column); + const contents &get(int row, int column) const; + //!< retrieves the contents from the specified "row" and "column". + + bool put(int row, int column, const contents &to_put); + //!< stores "to_put" into the matrix at "row" and "column". + + contents *operator[] (int row); + //!< dangerous: indexes by "row" into the underlying contents. + /*!< the result can be used as a pointer to the whole row of data, or it + can be indexed into by column to find a row/column position. this is + dangerous if one is not careful about ensuring that the column used is + within bounds. if the "row" parameter is out of bounds, then NIL is + returned. */ + const contents *operator[] (int row) const; + //!< dangerous: constant peek into data for "row" in underlying contents. + + matrix submatrix(int row, int column, int rows, int columns) const; + //!< returns a submatrix of this one starting at "row" and "column". + /*!< The returned matrix should contain "rows" rows and "columns" columns. + if the size or position are out of bounds, an empty matrix is returned. */ + void submatrix(matrix &sub, int row, int column, int rows, int cols) const; + //!< like submatrix() above, but stores into the "sub" parameter. + + matrix transpose() const; + //!< provides the transposed form of this matrix. + void transpose(matrix &resultant) const; + //!< stores the transpose of this matrix in "resultant". + + basis::array get_row(int row); + //!< return the vector at the "row", or an empty array if "row" is invalid. + basis::array get_column(int column); + //!< return the vector at the "column", or an empty array if it's invalid. + + bool insert_row(int before_row); + //!< inserts a row before the "before_" parameter. + /*!< all existing data is preserved in the matrix, although it may get + moved over depending on where it's located with respect to "before_row". */ + bool insert_column(int before_column); + //!< inserts a column before the "before_" parameter. + + void reset(int rows = 0, int columns = 0); + //!< empties the matrix and changes its size. + + void redimension(int new_rows, int new_columns); + //!< changes the size to contain "new_rows" by "new_columns" elements. + /*!< data that was held previously stays in the array as long as its row + and column indices are still valid. */ + + bool zap_row(int row_to_zap); + bool zap_column(int column_to_zap); + //!< removes a row or column from the matrix. + /*!< the matrix becomes one row or column less than before and all data + from the deleted vector is lost. */ + +private: + int _rows; //!< number of rows in the matrix. + int _cols; //!< number of columns in the matrix. + + int compute_index(int row, int column) const; + //!< returns the flat index that corresponds to the "row" and "column". +}; + +////////////// + +//! A matrix of integers. +class int_matrix : public matrix +{ +public: + int_matrix(int rows = 0, int cols = 0, int *data = NIL) + : matrix(rows, cols, data) {} + int_matrix(const matrix &to_copy) : matrix(to_copy) {} +}; + +//! A matrix of strings. +class string_matrix : public matrix +{ +public: + string_matrix(int rows = 0, int cols = 0, basis::astring *data = NIL) + : matrix(rows, cols, data) {} + string_matrix(const matrix &to_copy) : matrix(to_copy) {} +}; + +//! A matrix of double floating point numbers. +class double_matrix : public matrix +{ +public: + double_matrix(int rows = 0, int cols = 0, double *data = NIL) + : matrix(rows, cols, data) {} + double_matrix(const matrix &to_copy) : matrix(to_copy) {} +}; + +////////////// + +// implementation for longer methods... + +//hmmm: the operations for zapping use extra memory. they could easily +// be done as in-place copies. + +#undef static_class_name +#define static_class_name() "matrix" + // used in bounds_halt macro. + +template +matrix::matrix(int r, int c, contents *dat) +: basis::array(r*c, dat), _rows(r), _cols(c) {} + +template +matrix::matrix(const matrix &to_copy) +: basis::array(0), _rows(0), _cols(0) +{ *this = to_copy; } + +template +matrix &matrix::operator = (const matrix &to_copy) +{ + if (&to_copy == this) return *this; + basis::array::operator = (to_copy); + _rows = to_copy._rows; + _cols = to_copy._cols; + return *this; +} + +template +contents *matrix::operator[] (int r) +{ return &basis::array::operator [] (compute_index(r, 0)); } + +template +const contents *matrix::operator[] (int r) const +{ return &basis::array::operator [] (compute_index(r, 0)); } + +template +const contents &matrix::get(int r, int c) const +{ return basis::array::get(compute_index(r, c)); } + +template +contents &matrix::get(int row, int column) +{ return basis::array::operator [] (compute_index(row, column)); } + +template +int matrix::compute_index(int row, int column) const +{ return column + row * _cols; } + +template +void matrix::reset(int rows_in, int columns_in) +{ + if ( (_rows == rows_in) && (_cols == columns_in) ) { + // reuse space, but reset the objects to their initial state. + for (int i = 0; i < basis::array::length(); i++) + basis::array::operator [](i) = contents(); + return; + } + + _rows = 0; + _cols = 0; + basis::array::reset(0); + + this->insert(0, rows_in * columns_in); + _rows = rows_in; + _cols = columns_in; +} + +template +void matrix::redimension(int new_rows, int new_columns) +{ + if ( (_rows == new_rows) && (_cols == new_columns) ) return; + matrix new_this(new_rows, new_columns); + for (int r = 0; r < minimum(new_rows, rows()); r++) + for (int c = 0; c < minimum(new_columns, columns()); c++) + new_this[r][c] = (*this)[r][c]; + *this = new_this; +} + +template +bool matrix::put(int row, int column, const contents &to_put) +{ + if ( (row >= rows()) || (column >= columns()) ) + return false; + (operator [](row))[column] = to_put; + return true; +} + +template +matrix matrix::submatrix(int row, int column, int rows_in, + int columns_in) const +{ + matrix to_return; + submatrix(to_return, row, column, rows_in, columns_in); + return to_return; +} + +template +void matrix::submatrix(matrix &sub, int row, int column, + int rows_in, int columns_in) const +{ + sub.reset(); + if ( (row >= rows()) || (row + rows_in >= rows()) ) return; + if ( (column >= columns()) || (column + columns_in >= columns()) ) return; + sub.reset(rows_in, columns_in); + for (int r = row; r < row + rows_in; r++) + for (int c = column; c < column + columns_in; c++) + sub[r - row][c - column] = (*this)[r][c]; +} + +template +matrix matrix::transpose() const +{ + matrix to_return; + transpose(to_return); + return to_return; +} + +template +void matrix::transpose(matrix &resultant) const +{ + resultant.reset(columns(), rows()); + for (int i = 0; i < rows(); i++) + for (int j = 0; j < columns(); j++) + resultant[j][i] = (*this)[i][j]; +} + +template +basis::array matrix::get_row(int row) +{ + basis::array to_return; + if (row >= rows()) return to_return; + to_return.reset(columns()); + for (int i = 0; i < columns(); i++) + to_return[i] = get(row, i); + return to_return; +} + +template +basis::array matrix::get_column(int column) +{ + basis::array to_return; + if (column >= columns()) return to_return; + to_return.reset(rows()); + for (int i = 0; i < rows(); i++) + to_return[i] = get(i, column); + return to_return; +} + +template +bool matrix::zap_row(int row_to_zap) +{ + FUNCDEF("zap_row"); + bounds_halt(row_to_zap, 0, rows() - 1, false); + const int start = compute_index(row_to_zap, 0); + // this is only safe because the indices are stored in row-major order (which + // i hope means the right thing). in any case, the order is like so: + // 1 2 3 4 + // 5 6 7 8 + // thus we can whack a whole row contiguously. + basis::array::zap(start, start + columns() - 1); + _rows--; + return true; +} + +template +bool matrix::zap_column(int column_to_zap) +{ + FUNCDEF("zap_column"); + bounds_halt(column_to_zap, 0, columns() - 1, false); + // this starts at the end, which keeps the indexes meaningful. otherwise + // the destruction interferes with finding the elements. + for (int r = rows(); r >= 0; r--) { + const int loc = compute_index(r, column_to_zap); + basis::array::zap(loc, loc); + } + _cols--; + return true; +} + +template +bool matrix::insert_row(int position) +{ + FUNCDEF("insert_row"); + bounds_halt(position, 0, rows(), false); + // see comment in zap_row for reasoning about the below. + basis::array::insert(compute_index(position, 0), columns()); + _rows++; + // clear out those spaces. + for (int c = 0; c < columns(); c++) + put(position, c, contents()); + return true; +} + +template +bool matrix::insert_column(int position) +{ + FUNCDEF("insert_column"); + bounds_halt(position, 0, columns(), false); + // similarly to zap_column, we must iterate in reverse. + for (int r = rows(); r >= 0; r--) + basis::array::insert(compute_index(r, position), 1); + _cols++; + // clear out those spaces. + for (int r = 0; r < rows(); r++) + put(r, position, contents()); + return true; +} + +#undef static_class_name + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/memory_limiter.cpp b/nucleus/library/structures/memory_limiter.cpp new file mode 100644 index 00000000..b0756a8a --- /dev/null +++ b/nucleus/library/structures/memory_limiter.cpp @@ -0,0 +1,171 @@ +/*****************************************************************************\ +* * +* Name : memory_limiter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "int_hash.h" +#include "memory_limiter.h" + +#include + +#include + +using namespace basis; + +namespace structures { + +#undef LOG +#define LOG(to_print) printf("%s\n", astring(to_print).s()) + +class ml_memory_record +{ +public: + int _usage; + + ml_memory_record(int initial) : _usage(initial) {} +}; + +////////////// + +class ml_memory_state_meter : public int_hash +{ +public: + ml_memory_state_meter() : int_hash(10) {} +}; + +////////////// + +memory_limiter::memory_limiter(int overall_limit, int individual_limit) +: _overall_limit(overall_limit), + _individual_limit(individual_limit), + _overall_size(0), + _individual_sizes(new ml_memory_state_meter) +{ +} + +memory_limiter::~memory_limiter() +{ + WHACK(_individual_sizes); +} + +void memory_limiter::reset() +{ + _overall_size = 0; + _individual_sizes->reset(); +} + +const int_set &memory_limiter::individuals_listed() const +{ return _individual_sizes->ids(); } + +ml_memory_record *memory_limiter::find_individual(int individual) const +{ + ml_memory_record *to_return = NIL; + if (!_individual_sizes->find(individual, to_return)) return NIL; + // no record for that guy. + return to_return; +} + +int memory_limiter::individual_usage(int individual) const +{ + ml_memory_record *found = find_individual(individual); + if (!found) return 0; + return found->_usage; +} + +int memory_limiter::individual_space_left(int individual) const +{ + if (!individual_limit()) return 0; + return individual_limit() - individual_usage(individual); +} + +astring memory_limiter::text_form(int indent) const +{ + astring to_return; + astring indentat(' ', indent); + + astring allowed = overall_limit()? + astring(astring::SPRINTF, "%dK", overall_limit() / KILOBYTE) + : "unlimited"; + astring avail = overall_limit()? + astring(astring::SPRINTF, "%dK", overall_space_left() / KILOBYTE) + : "unlimited"; + + to_return += astring(astring::SPRINTF, "Overall Limit=%s, Allocations=%dK, " + "Free Space=%s", allowed.s(), overall_usage() / KILOBYTE, avail.s()); + to_return += "\n"; + + int_set individuals = _individual_sizes->ids(); + for (int i = 0; i < individuals.elements(); i++) { + astring allowed = individual_limit()? + astring(astring::SPRINTF, "%dK", individual_limit() / KILOBYTE) + : "unlimited"; + astring avail = individual_limit()? + astring(astring::SPRINTF, "%dK", + individual_space_left(individuals[i]) / KILOBYTE) : "unlimited"; + + to_return += indentat + astring(astring::SPRINTF, "individual %d: " + "Limit=%s, Used=%dK, Free=%s", individuals[i], allowed.s(), + individual_usage(individuals[i]) / KILOBYTE, avail.s()); + to_return += "\n"; + } + if (!individuals.elements()) { + to_return += indentat + "No allocations owned currently."; + to_return += "\n"; + } + return to_return; +} + +bool memory_limiter::okay_allocation(int individual, int memory_desired) +{ +// FUNCDEF("okay_allocation"); + // check the overall allocation limits first. + if (_overall_limit + && (_overall_size + memory_desired > _overall_limit) ) return false; + // now check sanity of this request. + if (_individual_limit && (memory_desired > _individual_limit) ) return false; + // now check the allocations per user. + ml_memory_record *found = find_individual(individual); + if (!found) { + _individual_sizes->add(individual, new ml_memory_record(0)); + found = find_individual(individual); + if (!found) { + LOG("ERROR: adding a new record to the memory state!"); + return false; + } + } + if (_individual_limit + && (found->_usage + memory_desired > _individual_limit) ) + return false; + found->_usage += memory_desired; + _overall_size += memory_desired; + return true; +} + +bool memory_limiter::record_deletion(int individual, int memory_deleted) +{ + if (memory_deleted < 0) return false; // bogus. + // make sure the individual exists. + ml_memory_record *found = find_individual(individual); + if (!found) return false; + // the individual must have actually allocated at least that much previously. + if (found->_usage < memory_deleted) return false; + // okay, we think that's reasonable. + found->_usage -= memory_deleted; + _overall_size -= memory_deleted; + // clean out an empty locker. + if (!found->_usage) _individual_sizes->zap(individual); + return true; +} + +} //namespace. + + diff --git a/nucleus/library/structures/memory_limiter.h b/nucleus/library/structures/memory_limiter.h new file mode 100644 index 00000000..1cb0e9fc --- /dev/null +++ b/nucleus/library/structures/memory_limiter.h @@ -0,0 +1,116 @@ +#ifndef MEMORY_LIMITER_CLASS +#define MEMORY_LIMITER_CLASS + +/*****************************************************************************\ +* * +* Name : memory_limiter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace structures { + +// forward. +class ml_memory_record; +class ml_memory_state_meter; + +//! Tracks memory currently in use by memory manager objects. +/*! + The manager is given the ability to control overall memory usage as well + as to track memory usage per each user of the memory (assuming that the + memory is granted to unique users indexable via an integer). +*/ + +class memory_limiter +{ +public: + memory_limiter(int overall_limit, int individual_limit); + //!< creates a limiter that allows "overall_limit" bytes to be in use. + /*!< any attempts to add new memory after that limit is reached will be + rejected. if "overall_limit" is zero, then no limit is enforced on the + amount of memory that can be used in total. the "individual_limit" + specifies per-user limits, where each user of memory is identified by a + unique integer and where all users are granted equal rights to allocate + memory. "individual_limit" can also be given as zero, meaning no limit + is enforced per individual. note that "overall_limit" should usually be + many multiples of the "individual_limit", as appropriate to how many users + are expected. */ + + virtual ~memory_limiter(); + + DEFINE_CLASS_NAME("memory_limiter"); + + int overall_limit() const { return _overall_limit; } + //!< returns the current overall limit. + int individual_limit() const { return _individual_limit; } + //!< returns the current individual limit. + + int overall_usage() const { return _overall_size; } + //!< returns the size used by all managed memory. + + int overall_space_left() const { return overall_limit() - overall_usage(); } + //!< returns the overall space left for allocation. + + int individual_usage(int individual) const; + //!< returns the amount of memory used by "individual". + + int individual_space_left(int individual) const; + //!< returns the space left for the individual specified. + + basis::astring text_form(int indent = 0) const; + //!< returns a string that lists out the overall plus individual limits. + /*!< "indent" is used for spacing the printed rows of information. */ + + bool okay_allocation(int individual, int memory_desired); + //!< returns true if "individual" may allocate "memory_desired" bytes. + /*!< false indicates that this memory must not be allocated if the limits + are to be adhered to, either because there is already too much used in + the system at large or because this user is already using their limit. */ + + bool record_deletion(int individual, int memory_deleted); + //!< acknowledges that the "individual" freed "memory_deleted" bytes. + /*!< returns true if the "individual" is known and if "memory_deleted" + could be subtracted from that object's usage count. failure of this method + indicates that this class is not being used properly; if memory was + okayed to be granted in okay_allocation, then the paired record_deletion + will always succeed (in any arbitrary order where the okay_allocation + succeeds and proceeds the matching record_deletion). if there are no + remaining allocations for this individual, then its record is removed. */ + + void reset(); + //!< returns the object to a pristine state. + + const structures::int_set &individuals_listed() const; + //!< reports the current set of individuals using memory. + /*!< to know whether one is using this class appropriately, check the + returned set. if one does not think there should be any memory in use, + then the set should be empty and overall_usage() should return zero. + if the overall_usage() is zero, but there are members in the set, then + there is an implementation error in memory_limiter. otherwise, if the + set is non-empty, then deleted memory has not been recorded. */ + +private: + int _overall_limit; //!< how many total bytes allowed? + int _individual_limit; //!< how many bytes may each user consume? + int _overall_size; //!< the current measured overall memory usage in bytes. + ml_memory_state_meter *_individual_sizes; //!< tracks memory per individual. + + ml_memory_record *find_individual(int individual) const; + //!< locates the record held for the "individual" specified or returns NIL. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/object_packers.cpp b/nucleus/library/structures/object_packers.cpp new file mode 100644 index 00000000..233ee8ad --- /dev/null +++ b/nucleus/library/structures/object_packers.cpp @@ -0,0 +1,279 @@ +/*****************************************************************************\ +* * +* Name : object_packers * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "object_packers.h" + +#include + +using namespace basis; + +namespace structures { + +// rotate_in and snag_out do most of the real "work", if any. + +void rotate_in(byte_array &attach_into, int to_attach, int size_in_bytes) +{ + basis::un_int temp = basis::un_int(to_attach); + for (int i = 0; i < size_in_bytes; i++) { + attach_into += abyte(temp % 0x100); + temp >>= 8; + } +} + +void snag_out(byte_array &eat_from, basis::un_int &accumulator, int size_in_bytes) +{ + accumulator = 0; + for (int i = 0; i < size_in_bytes; i++) { + accumulator <<= 8; + accumulator += eat_from[size_in_bytes - i - 1]; + } + eat_from.zap(0, size_in_bytes - 1); +} + +////////////// + +int packed_size(const byte_array &packed_form) +{ return 2 * sizeof(int) + packed_form.length(); } + +void attach(byte_array &packed_form, const byte_array &to_attach) +{ + obscure_attach(packed_form, to_attach.length()); + packed_form += to_attach; +} + +bool detach(byte_array &packed_form, byte_array &to_detach) +{ + un_int len = 0; + if (!obscure_detach(packed_form, len)) return false; + if (packed_form.length() < (int)len) return false; + to_detach = packed_form.subarray(0, len - 1); + packed_form.zap(0, len - 1); + return true; +} + +////////////// + +// these are the only "real" attach/detach functions on number types. the +// others are all faking it by calling these. + +void attach(byte_array &packed_form, basis::un_int to_attach) +{ rotate_in(packed_form, to_attach, 4); } + +bool detach(byte_array &packed_form, basis::un_int &to_detach) +{ + if (packed_form.length() < 4) return false; + basis::un_int temp; + snag_out(packed_form, temp, 4); + to_detach = basis::un_int(temp); + return true; +} + +void attach(byte_array &packed_form, basis::un_short to_attach) +{ rotate_in(packed_form, to_attach, 2); } + +bool detach(byte_array &packed_form, basis::un_short &to_detach) +{ + if (packed_form.length() < 2) return false; + basis::un_int temp; + snag_out(packed_form, temp, 2); + to_detach = basis::un_short(temp); + return true; +} + +void attach(byte_array &packed_form, abyte to_attach) +{ packed_form += to_attach; } + +bool detach(byte_array &packed_form, abyte &to_detach) +{ + if (packed_form.length() < 1) return false; + to_detach = packed_form[0]; + packed_form.zap(0, 0); + return true; +} + +////////////// + +void attach(byte_array &packed_form, int to_attach) +{ attach(packed_form, basis::un_int(to_attach)); } + +bool detach(byte_array &packed_form, int &to_detach) +{ return detach(packed_form, (basis::un_int &)to_detach); } + +//void attach(byte_array &packed_form, basis::un_long to_attach) +//{ attach(packed_form, basis::un_int(to_attach)); } + +//bool detach(byte_array &packed_form, basis::un_long &to_detach) +//{ return detach(packed_form, (basis::un_int &)to_detach); } + +//void attach(byte_array &packed_form, long to_attach) +//{ attach(packed_form, basis::un_int(to_attach)); } + +//bool detach(byte_array &packed_form, long &to_detach) +//{ return detach(packed_form, (basis::un_int &)to_detach); } + +void attach(byte_array &packed_form, short to_attach) +{ attach(packed_form, basis::un_short(to_attach)); } + +bool detach(byte_array &packed_form, short &to_detach) +{ return detach(packed_form, (basis::un_short &)to_detach); } + +void attach(byte_array &packed_form, char to_attach) +{ attach(packed_form, abyte(to_attach)); } + +bool detach(byte_array &packed_form, char &to_detach) +{ return detach(packed_form, (abyte &)to_detach); } + +void attach(byte_array &packed_form, bool to_attach) +{ attach(packed_form, abyte(to_attach)); } + +////////////// + +// can't assume that bool is same size as byte, although it should fit +// into a byte just fine. +bool detach(byte_array &packed_form, bool &to_detach) +{ + abyte chomp; + if (!detach(packed_form, chomp)) return false; + to_detach = !!chomp; + return true; +} + +// operates on a number less than 1.0 that we need to snag the next digit +// to the right of the decimal point from. +double break_off_digit(double &input) { +//printf(astring(astring::SPRINTF, "break input=%f\n", input).s()); + input *= 10.0; +//printf(astring(astring::SPRINTF, "after mult=%f\n", input).s()); + double mod_part = fmod(input, 1.0); +//printf(astring(astring::SPRINTF, "modded=%f\n", mod_part).s()); + double to_return = input - mod_part; +//printf(astring(astring::SPRINTF, "to ret=%f\n", to_return).s()); + input -= to_return; + return to_return; +} + +//hmmm: not very efficient! it's just packing and wasting bytes doing it... +int packed_size(double to_pack) +{ + byte_array packed; + attach(packed, to_pack); + return packed.length(); +} + +void attach(byte_array &packed_form, double to_pack) +{ + int exponent = 0; + double mantissa = frexp(to_pack, &exponent); + abyte pos = mantissa < 0.0? false : true; + mantissa = fabs(mantissa); +//printf("mant=%10.10f pos=%d expon=%d\n", mantissa, int(pos), exponent); + packed_form += pos; + attach(packed_form, exponent); + byte_array mantis; + // even if the double has 52 bits for mantissa (where ms docs say 44), + // a 16 digit bcd encoded number should handle the size (based on size of + // 2^52 in digits). + for (int i = 0; i < 9; i++) { + double dig1 = break_off_digit(mantissa); +//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig1)).s()); + double dig2 = break_off_digit(mantissa); +//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig2)).s()); + mantis += abyte(dig1 * 16 + dig2); + } + attach(packed_form, mantis); +//printf("attach exit\n"); +} + +bool detach(byte_array &packed_form, double &to_unpack) +{ +//printf("detach entry\n"); + if (packed_form.length() < 1) return false; // no sign byte. + abyte pos = packed_form[0]; +//printf(astring(astring::SPRINTF, "pos=%d\n", int(pos)).s()); + packed_form.zap(0, 0); + int exponent; + if (!detach(packed_form, exponent)) return false; +//printf(astring(astring::SPRINTF, "expon=%d\n", exponent).s()); + byte_array mantis; + if (!detach(packed_form, mantis)) return false; + double mantissa = 0; + for (int i = mantis.last(); i >= 0; i--) { + abyte chop = mantis[i]; + double dig1 = chop / 16; +//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig1)).s()); + double dig2 = chop % 16; +//printf(astring(astring::SPRINTF, "break digit=%d\n", int(dig2)).s()); + mantissa += dig2; + mantissa /= 10; + mantissa += dig1; + mantissa /= 10; + } +//printf(astring(astring::SPRINTF, "mant=%10.10f\n", mantissa).s()); + to_unpack = ldexp(mantissa, exponent); + if (!pos) to_unpack = -1.0 * to_unpack; +//printf("pos=%d\n", int(pos)); +//printf(astring(astring::SPRINTF, "to_unpack=%f\n", to_unpack).s()); +//printf("detach exit\n"); + return true; +} + +void attach(byte_array &packed_form, float to_pack) +{ attach(packed_form, double(to_pack)); } + +bool detach(byte_array &packed_form, float &to_unpack) +{ + double real_unpack; + bool to_return = detach(packed_form, real_unpack); + to_unpack = (float)real_unpack; + return to_return; +} + +////////////// + +void obscure_attach(byte_array &packed_form, un_int to_attach) +{ +//printf("initial value=%x\n", to_attach); + basis::un_int first_part = 0xfade0000; +//printf("first part curr=%x\n", first_part); + basis::un_int second_part = 0x0000ce0f; +//printf("second part curr=%x\n", second_part); + first_part = first_part | (to_attach & 0x0000ffff); +//printf("first part now=%x\n", first_part); + second_part = second_part | (to_attach & 0xffff0000); +//printf("second part now=%x\n", second_part); + attach(packed_form, first_part); + attach(packed_form, second_part); +} + +bool obscure_detach(byte_array &packed_form, un_int &to_detach) +{ + basis::un_int first_part; + basis::un_int second_part; + if (!detach(packed_form, first_part)) return false; + if (!detach(packed_form, second_part)) return false; +//printf("first part after unpack=%x\n", first_part); +//printf("second part after unpack=%x\n", second_part); + if (basis::un_int(first_part & 0xffff0000) != basis::un_int(0xfade0000)) return false; +//printf("first part with and=%x\n", first_part & 0xffff0000); + if (basis::un_int(second_part & 0x0000ffff) != basis::un_int(0x0000ce0f)) return false; +//printf("second part with and=%x\n", second_part & 0x0000ffff); + to_detach = int( (second_part & 0xffff0000) + (first_part & 0x0000ffff) ); +//printf("final result=%x\n", to_detach); + return true; +} + +////////////// + +} // namespace + diff --git a/nucleus/library/structures/object_packers.h b/nucleus/library/structures/object_packers.h new file mode 100644 index 00000000..fecb7876 --- /dev/null +++ b/nucleus/library/structures/object_packers.h @@ -0,0 +1,178 @@ +#ifndef OBJECT_PACKERS_CLASS +#define OBJECT_PACKERS_CLASS + +/*****************************************************************************\ +* * +* Name : object_packers * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace structures { + +// the sizes in bytes of common objects. if the compiler doesn't match these, there will +// probably be severe tire damage. +const int PACKED_SIZE_BYTE = 1; +const int PACKED_SIZE_INT16 = 2; +const int PACKED_SIZE_INT32 = 4; + +// these functions pack and unpack popular data types. + +void attach(basis::byte_array &packed_form, bool to_attach); + //!< Packs a bool "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, bool &to_detach); + //!< Unpacks a bool "to_detach" from "packed_form". + +void attach(basis::byte_array &packed_form, basis::abyte to_attach); + //!< Packs a byte "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, basis::abyte &to_detach); + //!< Unpacks a byte "to_detach" from "packed_form". + +int packed_size(const basis::byte_array &packed_form); + //!< Reports the size required to pack a byte array into a byte array. +void attach(basis::byte_array &packed_form, const basis::byte_array &to_attach); + //!< Packs a byte_array "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, basis::byte_array &to_detach); + //!< Unpacks a byte_array "to_detach" from "packed_form". + +void attach(basis::byte_array &packed_form, char to_attach); + //!< Packs a char "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, char &to_detach); + //!< Unpacks a char "to_detach" from "packed_form". + +int packed_size(double to_pack); + //!< Reports how large the "to_pack" will be as a stream of bytes. +void attach(basis::byte_array &packed_form, double to_pack); + //!< Packs a double precision floating point "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, double &to_unpack); + //!< Unpacks a double precision floating point "to_attach" from "packed_form". + +void attach(basis::byte_array &packed_form, float to_pack); + //!< Packs a floating point "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, float &to_unpack); + //!< Unpacks a floating point "to_attach" from "packed_form". + +void attach(basis::byte_array &packed_form, int to_attach); + //!< Packs an integer "to_attach" into "packed_form". + /*!< This method and the other simple numerical storage methods use a little + endian ordering of the bytes. They are platform independent with respect to + endianness and will reassemble the number properly on any platform. */ +bool detach(basis::byte_array &packed_form, int &to_detach); + //!< Unpacks an integer "to_attach" from "packed_form". + +void obscure_attach(basis::byte_array &packed_form, basis::un_int to_attach); + //!< like the normal attach but shifts in some recognizable sentinel data. + /*!< this is slightly more sure than a simple integer attachment. it can + be used to make sure upcoming data is probably a valid int. */ +bool obscure_detach(basis::byte_array &packed_form, basis::un_int &to_detach); + //!< shifts the number back and checks validity, false returned if corrupted. + +/* +void attach(basis::byte_array &packed_form, long to_attach); + //!< Packs a long integer "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, long &to_detach); + //!< Unpacks a long integer "to_attach" from "packed_form". +*/ + +void attach(basis::byte_array &packed_form, short to_attach); + //!< Packs a short integer "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, short &to_detach); + //!< Unpacks a short integer "to_attach" from "packed_form". + +void attach(basis::byte_array &packed_form, basis::un_int to_attach); + //!< Packs an unsigned integer "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, basis::un_int &to_detach); + //!< Unpacks an unsigned integer "to_attach" from "packed_form". + +/* +void attach(basis::byte_array &packed_form, basis::un_long to_attach); + //!< Packs an unsigned long integer "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, basis::un_long &to_detach); + //!< Unpacks an unsigned long integer "to_attach" from "packed_form". +*/ + +void attach(basis::byte_array &packed_form, basis::un_short to_attach); + //!< Packs an unsigned short integer "to_attach" into "packed_form". +bool detach(basis::byte_array &packed_form, basis::un_short &to_detach); + //!< Unpacks an unsigned short integer "to_attach" from "packed_form". + +////////////// + +// helpful template functions for packing. + +//! provides a way to pack any array that stores packable objects. +template +void pack_array(basis::byte_array &packed_form, const basis::array &to_pack) { + obscure_attach(packed_form, to_pack.length()); + for (int i = 0; i < to_pack.length(); i++) to_pack[i].pack(packed_form); +} + +//! provides a way to unpack any array that stores packable objects. +template +bool unpack_array(basis::byte_array &packed_form, basis::array &to_unpack) { + to_unpack.reset(); + basis::un_int len; + if (!obscure_detach(packed_form, len)) return false; + basis::array swappy_array(len, NIL, to_unpack.flags()); + // we create an array of the specified length to see if it's tenable. + if (!swappy_array.observe()) return false; // failed to allocate. + for (int i = 0; i < (int)len; i++) { + if (!swappy_array[i].unpack(packed_form)) + return false; + } + // now that we've got exactly what we want, plunk it into the result array. + swappy_array.swap_contents(to_unpack); + return true; +} + +//! provides space estimation for the objects to be packed. +template +int packed_size_array(const basis::array &to_pack) { + int to_return = sizeof(int) * 2; // obscure version uses double int size. + for (int i = 0; i < to_pack.length(); i++) + to_return += to_pack[i].packed_size(); + return to_return; +} + +//! Packs flat objects into an array of bytes. +/*! Similar to pack above, but operates on arrays with simple +objects that do not support functional pack and unpack. */ +template +void pack_simple(basis::byte_array &packed_form, const basis::array &to_pack) { + obscure_attach(packed_form, to_pack.length()); + for (int i = 0; i < to_pack.length(); i++) + attach(packed_form, to_pack[i]); +} + +//! Unpacks flat objects from an array of bytes. +/*! Similar to unpack above, but operates on arrays with simple +objects that do not support functional pack and unpack. */ +template +bool unpack_simple(basis::byte_array &packed_form, basis::array &to_unpack) { + to_unpack.reset(); + basis::un_int len; + if (!obscure_detach(packed_form, len)) return false; + basis::array swappy_array(len, NIL, to_unpack.flags()); + if (!swappy_array.observe()) return false; // failed to allocate. + for (int i = 0; i < len; i++) { + if (!detach(packed_form, swappy_array[i])) + return false; + } + swappy_array.swap_contents(to_unpack); + return true; +} + +} // namespace + +#endif + diff --git a/nucleus/library/structures/pointer_hash.h b/nucleus/library/structures/pointer_hash.h new file mode 100644 index 00000000..dce57a56 --- /dev/null +++ b/nucleus/library/structures/pointer_hash.h @@ -0,0 +1,134 @@ +#ifndef POINTER_HASH_CLASS +#define POINTER_HASH_CLASS + +/*****************************************************************************\ +* * +* Name : pointer_hash * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_hasher.h" +#include "hash_table.h" +#include "pointer_hash.h" +#include "set.h" + +// forward. +class pointer_set; + +namespace structures { + +//! A hash table for storing pointers. +/*! + Implements a hash table indexed on pointer values that maintains a separate + set to list the items that are presently in the hash table. This slows down + additions somewhat, but finds are not affected. The advantage of the + separate index is that the apply() method is much faster. +*/ + +template +class pointer_hash : public hash_table +{ +public: + pointer_hash(int estimated_elements); + ~pointer_hash(); + + const pointer_set &ids() const; + void ids(pointer_set &ids) const; + //!< provides the current list of valid identifiers. + + basis::outcome add(void *key, contents *to_store); + //!< overrides base add() and ensures that the id list stays up to date. + contents *acquire(void *key); + //!< overrides base acquire() by ensuring that the ids stay up to date. + bool zap(void *key); + //!< overrides base zap() method plus keeps id list updated. + void reset(); + //!< overrides base reset() and ensures that the id list stays up to date. + + typedef bool apply_function(const void * &key, contents ¤t, + void *data_link); + + void apply(apply_function *to_apply, void *data_link); + //!< operates on every item in the pointer_hash table. + +private: + pointer_set *_ids; + //!< a separate list of the identifiers stored here. + /*! this provides a fairly quick way to iterate rather than having to span + the whole hash table. it does slow down zap() a bit though. */ +}; + +////////////// + +// implementations for larger methods below... + +template +pointer_hash::pointer_hash(int estimated_elements) +: hash_table(rotating_byte_hasher(), estimated_elements), + _ids(new pointer_set) +{} + +template +pointer_hash::~pointer_hash() +{ WHACK(_ids); } + +template +const pointer_set &pointer_hash::ids() const { return *_ids; } + +template +void pointer_hash::ids(pointer_set &ids) const { ids = *_ids; } + +template +basis::outcome pointer_hash::add(void *key, contents *to_store) +{ + _ids->add(key); + return hash_table::add(key, to_store); +} + +template +contents *pointer_hash::acquire(void *key) +{ + _ids->remove(key); + return hash_table::acquire(key); +} + +template +bool pointer_hash::zap(void *key) +{ + _ids->remove(key); + return hash_table::zap(key); +} + +template +void pointer_hash::reset() +{ + _ids->clear(); + hash_table::reset(); +} + +template +void pointer_hash::apply(apply_function *to_apply, void *data_link) +{ + for (int i = 0; i < _ids->elements(); i++) { + void *current = (*_ids)[i]; + contents *found = hash_table::find(current); + if (!found) { + _ids->remove(current); + continue; + } + to_apply(current, *found, data_link); + } +} + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/structures/roller.h b/nucleus/library/structures/roller.h new file mode 100644 index 00000000..9308e710 --- /dev/null +++ b/nucleus/library/structures/roller.h @@ -0,0 +1,125 @@ +#ifndef ROLLER_CLASS +#define ROLLER_CLASS + +/*****************************************************************************\ +* * +* Name : roller * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace structures { + +//! Maintains a pseudo-unique identifier number and issues a new one on demand. +/*! + The unique id is templated on the type of identifier, but the type used must + support: + 1) assigment to a value, + 2) a greater than or equal operator (>=), and + 3) the increment operator (++). + Zero is often treated as the invalid / empty / inactive identifier, but + roller does not prevent its use since some ranges might need to bridge + between negative and positive numbers. +*/ + +template +class roller +{ +public: + roller(contents start_of_range, contents end_of_range); + //!< constructs a roller between the start and end ranges. + /*!< this constructs a roller that runs from the value "start_of_range" + and will stay smaller than "end_of_range" (unless the initial parameters + are screwed up; this class doesn't validate the start and end of range). + when the roller hits the end of range, then its value is reset to the + "start_of_range" again. */ + + ~roller(); + + // these report the constructor parameters. + contents minimum() { return _start_of_range; } + //!< the smallest value that the roller can have. + contents maximum() { return _end_of_range; } + //!< the outer limit of the roller; it should never reach this. + + contents next_id(); + //!< returns a unique (per instance of this type) id. + + contents current() const; + //!< returns the current id to be used; be careful! + /*!< this value will be the next one returned, so only look at the current + id and don't use it unwisely. this function is useful if you want to + assign an id provisionally but might not want to complete the issuance of + it. */ + + void set_current(contents new_current); + //!< allows the current id to be manipulated. + /*!< this must be done with care lest existing ids be re-used. */ + +private: + contents _current_id; //!< the next id to bring forth. + contents _start_of_range; //!< first possible value. + contents _end_of_range; //!< one more than last possible value. +}; + +////////////// + +//! A roller that's based on integers. This is the most common type so far. + +class int_roller : public roller +{ +public: + int_roller(int start_of_range, int end_of_range) + : roller(start_of_range, end_of_range) {} +}; + +////////////// + +// implementations below... + +template +roller::roller(contents start, contents end) +: _current_id(start), _start_of_range(start), _end_of_range(end) {} + +template +void roller::set_current(contents new_current) +{ + _current_id = new_current; + if (_current_id >= _end_of_range) _current_id = _start_of_range; +} + +template roller::~roller() {} + +template contents roller::current() const +{ return _current_id; } + +template contents roller::next_id() +{ + contents to_return = _current_id; + if (to_return == _end_of_range) { + // somehow the id to return is at the end of the range. this probably + // means the end of range condition wasn't detected last time due to an + // error in the parameters or the operation of == or ++ in the templated + // class. + _current_id = _start_of_range; + to_return = _current_id; + } + _current_id++; // next id. + if (_current_id == _end_of_range) _current_id = _start_of_range; + // reset the current position when hits the end of the range. + return to_return; +} + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/set.h b/nucleus/library/structures/set.h new file mode 100644 index 00000000..3215080f --- /dev/null +++ b/nucleus/library/structures/set.h @@ -0,0 +1,315 @@ +#ifndef SET_CLASS +#define SET_CLASS + +/*****************************************************************************\ +* * +* Name : set * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "object_packers.h" +#include "string_array.h" + +#include +#include +#include +#include + +namespace structures { + +//! Emulates a mathematical set, providing several standard set operations. +/*! + Note: this is not an efficient object and it should not be used for + sets of non-trivial sizes. +*/ + +template +class set : public basis::array +{ +public: + //! Constructs a set with "num" elements, copying them from "init". + /*! Be very careful to ensure that the array "init" has sufficient length + for "num" elements to be copied from it. */ + set(int num = 0, const contents *init = NIL, + basis::un_short flags = basis::array::EXPONE) + : basis::array(num, init, flags) {} + + ~set() {} //!< Destroys any storage held for the set. + + int elements() const { return this->length(); } + //!< Returns the number of elements in this set. + + bool empty() const { return elements() == 0; } + //!< Returns true if the set has no elements. + bool non_empty() const { return elements() != 0; } + //!< Returns true if the set has some elements. + + void clear() { this->reset(); } //!< Empties out this set. + + bool member(const contents &to_test) const; + //!< Returns true if the item "to_test" is a member of this set. + + bool add(const contents &to_add); + //!< Adds a new element "to_add" to the set. + /*!< This always succeeds, but will return true if the item was not + already present. */ + + set &operator += (const contents &to_add) + { add(to_add); return *this; } + //!< An algebraic operator synonym for add() that operates on the contents. + set &operator += (const set &to_add) + { unionize(to_add); return *this; } + //!< An algebraic operator synonym for add() that operates on a set. + + bool remove(const contents &to_remove); + //!< Removes the item "to_remove" from the set. + /*!< If it was not present, false is returned and the set is unchanged. */ + + set &operator -= (const contents &to_zap) + { remove(to_zap); return *this; } + //!< An algebraic operator synonym for remove that operates on the contents. + set &operator -= (const set &to_zap) + { differentiate(to_zap); return *this; } + //!< An algebraic operator synonym for remove that operates on a set. + + set set_union(const set &union_with) const; + //!< Implements the set union of "this" with "union_with". + /*!< This returns the set formed from the union of "this" set with the set + specified in "union_with". (unfortunately, the name "set_union" must + be used to distinguish from the C keyword "union".) */ + + void unionize(const set &union_with); + //!< Makes "this" set a union of "this" and "union_with". + + set operator + (const set &uw) const { return set_union(uw); } + //!< A synonym for set_union. + + set intersection(const set &intersect_with) const; + //!< Returns the intersection of "this" with the set in "intersect_with". + + set operator * (const set &iw) const { return intersection(iw); } + //!< A synonym for intersection. + + set difference(const set &differ_with) const; + //!< Returns the difference of this with "differ_with". + /*!< Difference is defined as the subset of elements in "this" that are not + also in "differ_with". */ + + void differentiate(const set &differ_with); + //!< Makes "this" set equal to the difference of "this" and "differ_with". + /*!< That is, after the call, "this" will only contain elements that were + not also in "differ_with". */ + + set operator - (const set &dw) const { return difference(dw); } + //!< A synonym for difference. + + int find(const contents &to_find) const; + //!< Returns the integer index of the item "to_find" in this set. + /*!< This returns a negative number if the index cannot be found. Note + that this only makes sense within our particular implementation of set as + an array. */ + + //! Zaps the entry at the specified "index". + /*! This also treats the set like an array. The index must be within the + bounds of the existing members. */ + bool remove_index(int index) + { return this->zap(index, index) == basis::common::OKAY; } +}; + +////////////// + +//! provides a way to pack any set that stores packable objects. +template +void pack(basis::byte_array &packed_form, const set &to_pack) +{ + obscure_attach(packed_form, to_pack.elements()); + for (int i = 0; i < to_pack.elements(); i++) to_pack[i].pack(packed_form); +} + +//! provides a way to unpack any set that stores packable objects. +template +bool unpack(basis::byte_array &packed_form, set &to_unpack) +{ + to_unpack.clear(); + basis::un_int len; + if (!obscure_detach(packed_form, len)) return false; + contents to_fill; + for (int i = 0; i < (int)len; i++) { + if (!to_fill.unpack(packed_form)) return false; + to_unpack.add(to_fill); + } + return true; +} + +////////////// + +//! A simple object that wraps a templated set of ints. +class int_set : public set, public virtual basis::root_object +{ +public: + int_set() {} + //!< Constructs an empty set of ints. + int_set(const set &to_copy) : set(to_copy) {} + //!< Constructs a copy of the "to_copy" array. + + DEFINE_CLASS_NAME("int_set"); +}; + +//! A simple object that wraps a templated set of strings. +class string_set : public set, public virtual basis::packable +{ +public: + string_set() {} + //!< Constructs an empty set of strings. + string_set(const set &to_copy) + : set(to_copy) {} + //!< Constructs a copy of the "to_copy" array. + string_set(const string_array &to_copy) { + for (int i = 0; i < to_copy.length(); i++) + add(to_copy[i]); + } + + DEFINE_CLASS_NAME("string_set"); + + bool operator == (const string_set &compare) const { + for (int i = 0; i < elements(); i++) + if (get(i) != compare.get(i)) return false; + return true; + } + + operator string_array() const { + string_array to_return; + for (int i = 0; i < length(); i++) + to_return += get(i); + return to_return; + } + + virtual void pack(basis::byte_array &packed_form) const + { structures::pack(packed_form, *this); } + virtual bool unpack(basis::byte_array &packed_form) + { return structures::unpack(packed_form, *this); } + virtual int packed_size() const { + int to_return = sizeof(int) * 2; // length packed in, using obscure. + for (int i = 0; i < length(); i++) + to_return += get(i).length() + 1; + return to_return; + } +}; + +//! A set of pointers that hides the platform's pointer size. +class pointer_set : public set +{ +public: + pointer_set() {} + //!< Constructs an empty set of void pointers. + pointer_set(const set &to_copy) : set(to_copy) + {} + //!< Constructs a copy of the "to_copy" array. +}; + +////////////// + +// implementation for longer functions is below... + +template +bool set::member(const contents &to_test) const +{ + for (int i = 0; i < elements(); i++) + if (to_test == this->get(i)) + return true; + return false; +} + +template +bool set::add(const contents &to_add) +{ + if (member(to_add)) return false; + concatenate(to_add); + return true; +} + +template +int set::find(const contents &to_find) const +{ + for (int i = 0; i < elements(); i++) + if (to_find == this->get(i)) + return i; + return basis::common::NOT_FOUND; +} + +template +bool set::remove(const contents &to_remove) +{ + for (int i = 0; i < elements(); i++) + if (to_remove == this->get(i)) { + this->zap(i, i); + return true; + } + return false; +} + +template +set set::intersection(const set &intersect_with) const +{ + set created(0, NIL, this->flags()); + const set *smaller = this; + const set *larger = &intersect_with; + if (elements() > intersect_with.elements()) { + // switch the smaller one into place. + smaller = &intersect_with; + larger = this; + } + for (int i = 0; i < smaller->length(); i++) + if (larger->member(smaller->get(i))) + created.concatenate(smaller->get(i)); + return created; +} + +template +set set::set_union(const set &union_with) const +{ + set created = *this; + for (int i = 0; i < union_with.elements(); i++) + created.add(union_with.get(i)); + return created; +} + +template +void set::unionize(const set &union_with) +{ + for (int i = 0; i < union_with.elements(); i++) + add(union_with.get(i)); +} + +template +set set::difference(const set &differ_with) const +{ + set created = *this; + for (int i = 0; i < differ_with.elements(); i++) { + if (created.member(differ_with[i])) + created.remove(differ_with[i]); + } + return created; +} + +template +void set::differentiate(const set &differ_with) +{ + for (int i = 0; i < differ_with.elements(); i++) { + if (member(differ_with[i])) + remove(differ_with[i]); + } +} + +} // namespace. + +#endif + diff --git a/nucleus/library/structures/stack.h b/nucleus/library/structures/stack.h new file mode 100644 index 00000000..edf3c6e5 --- /dev/null +++ b/nucleus/library/structures/stack.h @@ -0,0 +1,208 @@ +#ifndef STACK_CLASS +#define STACK_CLASS + +/*****************************************************************************\ +* * +* Name : stack * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace structures { + +//! An abstraction that represents a stack data structure. +/*! + This behaves like a standard stack of objects, but it additionally allows + access to any item in the stack via an array style bracket operator. +*/ + +template +class stack +{ +public: + enum stack_kinds { BOUNDED, UNBOUNDED }; + + stack(int elements = 0); + //!< Creates a stack with room for the specified number of "elements". + /*!< If "elements" is zero, then the stack is an UNBOUNDED stack that + has no set limit on the number of elements it can contain (besides the + amount of memory available). on an unbounded stack, a result of IS_FULL + will never be returned--instead a memory allocation failure would occur. + If "elements" is greater than zero, then the stack is a BOUNDED stack + which can hold at maximum "elements" number of objects. for bounded + stacks, if there is to be an allocation failure, it will happen at the + time of stack construction, rather than during execution. */ + + stack(const stack &to_copy); + //!< constructs a stack as a copy of "to_copy". + + ~stack(); + //!< destroys anything left on the stack. + + void reset(); + //!< throws out all contents on the stack. + + stack_kinds kind() const { return _kind; } + //!< returns the type of stack that was constructed. + + basis::outcome push(const contents &element); + //!< Enters a new element onto the top of the stack. + /*!< if the stack is too large to add another element, then IS_FULL is + returned. if the element to push is nil, the stack is unchanged and + IS_EMPTY is returned. */ + + basis::outcome pop(); + //!< Removes the top element on the stack. + /*!< If the stack has no elements to be popped off, then IS_EMPTY is + returned. The element that was popped is destroyed. */ + + contents &top(); + //!< Returns the top element from the stack but doesn't change the stack. + /*!< This method does not pop the element! If the stack is empty, then a + bogus contents object is returned. */ + + basis::outcome acquire_pop(contents &to_stuff); + //!< Used to grab the top off of the stack. + /*!< this is basically a call to top() followed by a pop(). if there was + no top, then IS_EMPTY is returned. */ + + int size() const; + //!< returns the size of the stack. + /*!< if the stack is empty, then 0 is returned. */ + + stack &operator =(const stack &to_copy); + //!< makes this stack a copy of "to_copy". + + contents &operator [](int index); + //!< Accesses the item at position "index" in the stack. + /*!< Allows access to the stack in an impure fashion; elements other than + the top can be examined. Efforts to access elements that do not exist + are ignored. The range for the element numbers is as in C and runs + from 0 to size() - 1. */ + + void invert(); + //!< Inverts this stack, meaning that the old bottom is the new top. + + int elements() const; + //!< Returns the number of elements used by the stack. + /*!< For a bounded stack, this returns the number of elements the stack + was constructed to hold. For an unbounded stack, it returns the current + number of elements (which is the same as size()). Note though that it is + different from size() for a bounded size stack! */ + +private: + basis::array _store; //!< holds the contents of the stack. + stack_kinds _kind; //!< the type of stack we've got. + int _valid_fields; //!< count of the number of items actually in use here. +}; + +////////////// + +// implementations below... + +template +stack::stack(int elements) +: _store(elements >= 0? elements : 0), + _kind(_store.length()? BOUNDED : UNBOUNDED), + _valid_fields(0) +{} + +template +stack::stack(const stack &to_copy) +: _store(0), _valid_fields(0) +{ operator = (to_copy); } + +template stack::~stack() {} + +template +int stack::size() const { return _valid_fields; } + +template +void stack::reset() +{ + while (pop() == basis::common::OKAY) {} // pop off elements until all are gone. +} + +template +int stack::elements() const { return _store.length(); } + +template +basis::outcome stack::push(const contents &element) +{ + if (_kind == BOUNDED) { + if (_valid_fields >= elements()) return basis::common::IS_FULL; + basis::outcome result = _store.put(_valid_fields, element); + if (result != basis::common::OKAY) return basis::common::IS_FULL; + } else _store.concatenate(element); + _valid_fields++; + return basis::common::OKAY; +} + +template +basis::outcome stack::pop() +{ + if (_valid_fields < 1) return basis::common::IS_EMPTY; + if (_kind == UNBOUNDED) + _store.zap(_store.length() - 1, _store.length() - 1); + _valid_fields--; + return basis::common::OKAY; +} + +template +contents &stack::top() +{ return _store[_valid_fields - 1]; } + +template +stack &stack::operator = (const stack &to_copy) +{ + if (this == &to_copy) return *this; + reset(); + _kind = to_copy._kind; + _store.reset(to_copy._store.length()); + for (int i = 0; i < to_copy._store.length(); i++) + _store.put(i, to_copy._store[i]); + _valid_fields = to_copy._valid_fields; + return *this; +} + +template +void stack::invert() +{ + for (int i = 0; i < _store.length() / 2; i++) { + contents hold = _store.get(i); + int exchange_index = _store.length() - i - 1; + _store.put(i, _store.get(exchange_index)); + _store.put(exchange_index, hold); + } +} + +template +contents &stack::operator [](int index) +{ + if (index >= _valid_fields) index = -1; // force a bogus return. + return _store[index]; +} + +template +basis::outcome stack::acquire_pop(contents &to_stuff) +{ + if (!_valid_fields) return basis::common::IS_EMPTY; + to_stuff = _store[_valid_fields - 1]; + if (_kind == UNBOUNDED) _store.zap(elements()-1, elements()-1); + _valid_fields--; + return basis::common::OKAY; +} + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/static_memory_gremlin.cpp b/nucleus/library/structures/static_memory_gremlin.cpp new file mode 100644 index 00000000..b84bbb69 --- /dev/null +++ b/nucleus/library/structures/static_memory_gremlin.cpp @@ -0,0 +1,250 @@ +/*****************************************************************************\ +* * +* Name : static_memory_gremlin * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2004-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "static_memory_gremlin.h" + +#include + +#include +//temp! needed for fake continuable error etc + +#include +#include + +using namespace basis; + +namespace structures { + +//#define DEBUG_STATIC_MEMORY_GREMLIN + // comment this out to eliminate debugging print-outs. otherwise they + // are controlled by the class interface. + +//#define SKIP_STATIC_CLEANUP + // don't uncomment this unless you want all static objects to be left + // allocated on shutdown of the program. that's very sloppy but may + // sometimes be needed for testing. + +////////////// + +const int SMG_CHUNKING_FACTOR = 32; + // we'll allocate this many indices at a time. + +////////////// + +static bool __global_program_is_dying = false; + // this is set to true when no more logging or access to static objects + // should be allowed. + +////////////// + +class gremlin_object_record +{ +public: + root_object *c_object; + const char *c_name; +}; + +////////////// + +static_memory_gremlin::static_memory_gremlin() +: c_lock(), + c_top_index(0), + c_actual_size(0), + c_pointers(NIL), + c_show_debugging(false) +{ + ensure_space_exists(); +} + +static_memory_gremlin::~static_memory_gremlin() +{ + __global_program_is_dying = true; + // now the rest of the program is on notice; we're practically gone. + +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + if (c_show_debugging) + printf("SMG: beginning static object shutdown...\n"); +#endif + +#ifndef SKIP_STATIC_CLEANUP + // clean up any allocated pointers in reverse order of addition. + while (c_top_index > 0) { + // make sure we fixate on which guy is shutting down. some new ones + // could be added on the end of the list as a result of this destruction. + int zapped_index = c_top_index - 1; + gremlin_object_record *ptr = c_pointers[zapped_index]; + c_pointers[zapped_index] = NIL; + // since we know the one we're zapping, we no longer need that index. + c_top_index--; + // this should allow us to keep chewing on items that are newly being + // added during static shutdown, since we have taken the current object + // entirely out of the picture. + if (ptr) { +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + if (c_show_debugging) + printf((astring("SMG: deleting ") + ptr->c_object->instance_name() + + " called " + ptr->c_name + + a_sprintf(" at index %d.\n", zapped_index) ).s()); +#endif + WHACK(ptr->c_object); + WHACK(ptr); + } + } +#endif + delete [] c_pointers; + c_pointers = NIL; +} + +bool static_memory_gremlin::__program_is_dying() { return __global_program_is_dying; } + +mutex &static_memory_gremlin::__memory_gremlin_synchronizer() +{ + static mutex __globabl_synch_mem; + return __globabl_synch_mem; +} + +int static_memory_gremlin::locate(const char *unique_name) +{ + auto_synchronizer l(c_lock); + for (int i = 0; i < c_top_index; i++) { + if (!strcmp(c_pointers[i]->c_name, unique_name)) return i; + } + return common::NOT_FOUND; +} + +root_object *static_memory_gremlin::get(const char *unique_name) +{ + auto_synchronizer l(c_lock); + int indy = locate(unique_name); + if (negative(indy)) return NIL; + return c_pointers[indy]->c_object; +} + +const char *static_memory_gremlin::find(const root_object *ptr) +{ + auto_synchronizer l(c_lock); + for (int i = 0; i < c_top_index; i++) { + if (ptr == c_pointers[i]->c_object) + return c_pointers[i]->c_name; + } + return NIL; +} + +bool static_memory_gremlin::put(const char *unique_name, root_object *to_put) +{ + auto_synchronizer l(c_lock); + int indy = locate(unique_name); + // see if that name already exists. + if (non_negative(indy)) { +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + if (c_show_debugging) + printf((astring("SMG: cleaning out old object ") + + c_pointers[indy]->c_object->instance_name() + + " called " + c_pointers[indy]->c_name + + " in favor of object " + to_put->instance_name() + + " called " + unique_name + + a_sprintf(" at index %d.\n", indy)).s()); +#endif + WHACK(c_pointers[indy]->c_object); + c_pointers[indy]->c_object = to_put; + return true; + } +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + if (c_show_debugging) + printf((astring("SMG: storing ") + to_put->instance_name() + + " called " + unique_name + + a_sprintf(" at index %d.\n", c_top_index)).s()); +#endif + ensure_space_exists(); + c_pointers[c_top_index] = new gremlin_object_record; + c_pointers[c_top_index]->c_object = to_put; + c_pointers[c_top_index]->c_name = unique_name; + c_top_index++; + return true; +} + +void static_memory_gremlin::ensure_space_exists() +{ + auto_synchronizer l(c_lock); + if (!c_pointers || (c_top_index + 1 >= c_actual_size) ) { + // never had any contents yet or not enough space exists. +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + if (c_show_debugging) + printf(a_sprintf("SMG: adding space for top at %d.\n", c_top_index).s()); +#endif + c_actual_size += SMG_CHUNKING_FACTOR; + typedef gremlin_object_record *base_ptr; + gremlin_object_record **new_ptr = new base_ptr[c_actual_size]; + if (!new_ptr) { + throw "error: static_memory_gremlin::ensure_space_exists: failed to allocate memory for pointer list"; + } + for (int i = 0; i < c_actual_size; i++) new_ptr[i] = NIL; + for (int j = 0; j < c_actual_size - SMG_CHUNKING_FACTOR; j++) + new_ptr[j] = c_pointers[j]; + if (c_pointers) delete [] c_pointers; + c_pointers = new_ptr; + } +} + +// this function ensures that the space for the global objects is kept until +// the program goes away. if it's the first time through, then the gremlin +// gets created; otherwise the existing one is used. this function should +// always be called by the main program before any attempts to use global +// features like SAFE_STATIC or the program wide logger. it is crucial that no +// user-level threads have been created in the program before this is called. +static_memory_gremlin &static_memory_gremlin::__hoople_globals() +{ + static bool _initted = false; // tells whether we've gone through yet. + static static_memory_gremlin *_internal_gremlin = NIL; + // holds our list of shared pieces... + + if (!_initted) { +#ifdef DEBUG_STATIC_MEMORY_GREMLIN + printf("%s: initializing HOOPLE_GLOBALS now.\n", _global_argv[0]); +#endif + +#ifdef ENABLE_MEMORY_HOOK + void *temp = program_wide_memories().provide_memory(1, __FILE__, __LINE__); + // invoke now to get memory engine instantiated. + program_wide_memories().release_memory(temp); // clean up junk. +#endif + +#ifdef ENABLE_CALLSTACK_TRACKING + program_wide_stack_trace().full_trace_size(); + // invoke now to get callback tracking instantiated. +#endif + FUNCDEF("HOOPLE_GLOBALS remainder"); + // this definition must be postponed until after the objects that would + // track it actually exist. + if (func) {} // shut up the compiler about using it. + + // this simple approach is not going to succeed if the SAFE_STATIC macros + // are used in a static library which is then used in more than one dynamic + // library on win32. this is because each dll in win32 will have a + // different version of certain static objects that should only occur once + // per program. this problem is due to the win32 memory model, but in + // hoople at least this has been prevented; our only static library that + // appears in a bunch of dlls is basis and it is not allowed to use the + // SAFE_STATIC macro. + _internal_gremlin = new static_memory_gremlin; + _initted = true; + } + + return *_internal_gremlin; +} + +////////////// + +} // namespace. + diff --git a/nucleus/library/structures/static_memory_gremlin.h b/nucleus/library/structures/static_memory_gremlin.h new file mode 100644 index 00000000..e53ee6d9 --- /dev/null +++ b/nucleus/library/structures/static_memory_gremlin.h @@ -0,0 +1,199 @@ +#ifndef STATIC_MEMORY_GREMLIN_CLASS +#define STATIC_MEMORY_GREMLIN_CLASS + +/*****************************************************************************\ +* * +* Name : static_memory_gremlin * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace structures { + +// forward declarations. +class gremlin_object_record; + +//! Holds onto memory chunks that are allocated globally within the program. +/*! + The objects managed by the gremlin do not get destroyed until after the + program begins shutting down. This file also provides the SAFE_STATIC macros + that can be used for allocating static objects safely in a multi-threaded + program. +*/ + +class static_memory_gremlin : public virtual basis::nameable +{ +public: + static_memory_gremlin(); + + ~static_memory_gremlin(); + /*!< when destroyed, it is assumed that the program's lifetime is over and + all objects stored here are now unnecessary. this implements a + regenerative scheme for when static shutdowns occur out of order; if an + object has already been destroyed, it is recreated for the purposes of + other statics being shutdown. eventually this should stabilize since + it's completely unacceptable for static objects to depend on each other + in a cycle. */ + + DEFINE_CLASS_NAME("static_memory_gremlin"); + + static bool __program_is_dying(); + //!< Reports whether the program is shutting down currently. + /*!< If this flag is true, then code should generally just return without + doing anything where possible. It's especially important for long + running loops or active threads to stop if they see this, although + the normal program exit processes usually make it unnecessary. */ + + static static_memory_gremlin &__hoople_globals(); + //!< Holds onto objects that have been allocated in a program-wide scope. + /*!< These objects will have a lifetime up to the point of normal static + object destruction and then they will be cleaned up. */ + + static basis::mutex &__memory_gremlin_synchronizer(); + //!< private object for static_memory_gremlin's use only. + + void enable_debugging(bool verbose) { c_show_debugging = verbose; } + //!< if "verbose" is true, then the object will produce a noisy log. + + bool put(const char *unique_name, basis::root_object *ptr); + //!< adds a "ptr" to the set of static objects under the "unique_name". + /*!< the name must really be unique or objects will collide. we recommend + using an identifier based on a line number and filename where the static + is going to be placed (see the safe static implementation below). */ + + basis::root_object *get(const char *unique_name); + //!< locates the pointer held for the "unique_name", if any. + /*!< if no pointer exists, then NIL is returned. NOTE: the returned + pointer must not be destroyed, since the object could be used at any time + during the program's lifetime. */ + + const char *find(const basis::root_object *ptr); + //!< locates the name for "ptr" in our objects. + /*!< if it does not exist, then NIL is returned. */ + + void ensure_space_exists(); + /*!< makes sure that the list of objects is large enough to contain all of + the identifiers that have been issued. */ + +private: + basis::mutex c_lock; //!< protects object's state. + int c_top_index; //!< top place that's occupied in the list. + int c_actual_size; //!< the real number of indices in list. + gremlin_object_record **c_pointers; //!< storage for the static pointers. + bool c_show_debugging; //!< if true, then the object will log noisily. + + int locate(const char *unique_name); + //!< returns the index number of the "unique_name". +}; + +////////////// + +//! Statically defines a singleton object whose scope is the program's lifetime. +/*! + SAFE_STATIC is a macro that dynamically creates a function returning a particular + data type. the object is allocated statically, so that the same object will be + returned ever after until the program is shut down. the allocation is guarded so + that multiple threads cannot create conflicting static objects. + + note: in ms-win32, if you use this macro in a static library (rather than + a DLL), then the heap context is different. thus you could actually have + multiple copies of the underlying object. if the object is supposed to + be global and unique, then that's a problem. relocating the definitions + to a dll while keeping declarations in the static lib works (see the + program wide logger approach in ). + "type" is the object class to return. + "func_name" is the function to be created. + "parms" must be a list of parameters in parentheses or nothing. + + example usage: @code + SAFE_STATIC(connection_table, conntab, (parm1, parm2)) @endcode + will define a function: connection_table &conntab() + that returns a connection table object which has been created safely, + given that the main synchronizer was called from the main thread at least + once. @code + SAFE_STATIC(astring, static_string_doodle, ) @endcode + will define a static astring object named "static_string_doodle" that uses + the default constructor for astrings. +*/ +#define SAFE_STATIC(type, func_name, parms) \ + type &func_name() { SAFE_STATIC_IMPLEMENTATION(type, parms, __LINE__); } + +//! this version returns a constant object instead. +#define SAFE_STATIC_CONST(type, func_name, parms) \ + const type &func_name() \ + { SAFE_STATIC_IMPLEMENTATION(type, parms, __LINE__); } + +//! Statically defines a string for the rest of the program's life. +/*! This macro can be used to make functions returning a string a little +simpler. This can only be used when the string is constant. The usual way +to use this macro is in a function that returns a constant reference to a +string. The macro allocates the string to be returned statically so that all +future calls will refer to the stored string rather than recreating it again. */ +#define STATIC_STRING(str) \ + SAFE_STATIC_IMPLEMENTATION(astring, (str), __LINE__) + +////////////// + +//! this macro creates a unique const char pointer based on file location. +#define UNIQUE_NAME_BASED_ON_SOURCE(name, linenum) \ + static const char *name = "file:" __FILE__ ":line:" #linenum + +//! this blob is just a chunk of macro implementation for SAFE_STATIC... +/*! if the static object isn't initialized yet, we'll create it and store it +in the static_memory_gremlin. we make sure that the program isn't shutting +down, because that imposes a new requirement--previously created statics might +have already been destroyed. thus, during the program shutdown, we carefully +recreate any objects that were already toast. */ +#define SAFE_STATIC_IMPLEMENTATION(type, parms, linenum) \ +/*hmmm: bring all this back.*/ \ +/* const char *func = "allocation"; */ \ +/* frame_tracking_instance __trail_of_function("safe_static", func, \ + __FILE__, __LINE__, true); */ \ + UNIQUE_NAME_BASED_ON_SOURCE(__uid_name, linenum); \ +/* program_wide_memories(); */ \ + static basis::root_object *_hidden = NIL; \ + /* if haven't initialized yet, then definitely need to lock carefully. */ \ + if (structures::static_memory_gremlin::__program_is_dying() || !_hidden) { \ + basis::auto_synchronizer l(structures::static_memory_gremlin::__memory_gremlin_synchronizer()); \ + if (structures::static_memory_gremlin::__program_is_dying()) { \ + /* we can't rely on the pointer since we're shutting down currently. */ \ + _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ + } \ + if (!_hidden) { /* make sure no one scooped us. */ \ + /* try to retrieve an existing one first and use it if there. */ \ + _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ + if (!_hidden) { \ + _hidden = new type parms ; /* create the object finally. */ \ + /* store our object using the unique name for it. */ \ + if (!structures::static_memory_gremlin::__hoople_globals().put(__uid_name, _hidden)) { \ + /* we failed to allocate space. this is serious. */ \ + throw __uid_name; \ + } \ + } \ + } \ + } \ + if (!_hidden) { \ + /* grab the pointer that was stored, in case we're late getting here. */ \ + /* another thread might have scooped the object creation. */ \ + _hidden = structures::static_memory_gremlin::__hoople_globals().get(__uid_name); \ + } \ + return *dynamic_cast(_hidden) + +// historical note: the SAFE_STATIC approach has existed since about 1998. +// however, the static_memory_gremlin's role in this started much later. + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/string_array.h b/nucleus/library/structures/string_array.h new file mode 100644 index 00000000..22d5c3f4 --- /dev/null +++ b/nucleus/library/structures/string_array.h @@ -0,0 +1,127 @@ +#ifndef STRING_ARRAY_CLASS +#define STRING_ARRAY_CLASS + +/*****************************************************************************\ +* * +* Name : string_array * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "object_packers.h" + +#include +#include +#include + +namespace structures { + +//! An array of strings with some additional helpful methods. + +class string_array +: public basis::array, + public virtual basis::packable, + public virtual basis::equalizable +{ +public: + string_array(int number = 0, const basis::astring *initial_contents = NIL) + : basis::array(number, initial_contents, + EXPONE | FLUSH_INVISIBLE) {} + //!< Constructs an array of "number" strings. + /*!< creates a list of strings based on an initial "number" of entries and + some "initial_contents", which should be a regular C array of astrings + with at least as many entries as "number". */ + + //! a constructor that operates on an array of char pointers. + /*! be very careful with the array to ensure that the right number of + elements is provided. */ + string_array(int number, const char *initial_contents[]) + : basis::array(number, NIL, EXPONE | FLUSH_INVISIBLE) { + for (int i = 0; i < number; i++) { + put(i, basis::astring(initial_contents[i])); + } + } + + string_array(const basis::array &to_copy) + : basis::array(to_copy) {} + //!< copy constructor that takes a templated array of astring. + + DEFINE_CLASS_NAME("string_array"); + + //! Prints out a formatted view of the contained strings and returns it. + basis::astring text_format(const basis::astring &separator = ",", + const basis::astring &delimiter = "\"") const { + basis::astring to_return; + for (int i = 0; i < length(); i++) { + to_return += delimiter; + to_return += get(i); + to_return += delimiter; + if (i < last()) + to_return += separator; + } + return to_return; + } + + basis::astring text_form() const { return text_format(); } + //!< A synonym for the text_format() method. + +//hmmm: extract as re-usable equality operation. + + //! Compares this string array for equality with "to_compare". + bool equal_to(const equalizable &to_compare) const { + const string_array *cast = cast_or_throw(to_compare, *this); + if (length() != cast->length()) + return false; + for (int i = 0; i < length(); i++) + if (cast->get(i) != get(i)) + return false; + return true; + } + + //! locates string specified and returns its index, or negative if missing. + int find(const basis::astring &to_find) const { + for (int i = 0; i < length(); i++) { + if (to_find.equal_to(get(i))) return i; + } + return basis::common::NOT_FOUND; + } + + //! Returns true if all of the elements in this are the same in "second". + /*! The array "second" can have more elements, but must have all of the + items listed in this string array. */ + bool prefix_compare(const string_array &second) const { + if (second.length() < length()) return false; + if (!length()) return false; + for (int i = 0; i < length(); i++) + if ((*this)[i] != second[i]) return false; + return true; + } + + //! Packs this string array into the "packed_form" byte array. + virtual void pack(basis::byte_array &packed_form) const + { pack_array(packed_form, *this); } + + //! Unpacks a string array from the "packed_form" byte array. + virtual bool unpack(basis::byte_array &packed_form) + { return unpack_array(packed_form, *this); } + + //! Returns the number of bytes this string array would consume if packed. + virtual int packed_size() const { + int to_return = sizeof(int) * 2; // length packed in, using obscure. + for (int i = 0; i < length(); i++) + to_return += get(i).length() + 1; + return to_return; + } +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/string_hash.h b/nucleus/library/structures/string_hash.h new file mode 100644 index 00000000..9a75ba9c --- /dev/null +++ b/nucleus/library/structures/string_hash.h @@ -0,0 +1,40 @@ +#ifndef STRING_HASH_CLASS +#define STRING_HASH_CLASS + +/*****************************************************************************\ +* * +* Name : string_hash * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "hash_table.h" +#include "string_hasher.h" + +#include + +namespace structures { + +//! Implements a hash table indexed on character strings. + +template +class string_hash : public hash_table +{ +public: + string_hash(int estimated_elements) + : hash_table(astring_hasher(), estimated_elements) {} + + ~string_hash() {} +}; + +} //namespace. + +#endif // outer guard. + diff --git a/nucleus/library/structures/string_hasher.cpp b/nucleus/library/structures/string_hasher.cpp new file mode 100644 index 00000000..0762e794 --- /dev/null +++ b/nucleus/library/structures/string_hasher.cpp @@ -0,0 +1,87 @@ +/*****************************************************************************\ +* * +* Name : string_hasher * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "string_hasher.h" + +#include +#include + +using namespace basis; + +namespace structures { + +const int MAX_STRING_CHARS_USED = 3; + // we use this many characters from the string in question (if they + // exist) at each end of the string. + +////////////// + +hashing_algorithm *string_hasher::clone() const +{ return new string_hasher; } + +basis::un_int string_hasher::hash(const void *key_data, int key_length_in) const +{ + if (!key_data) return 0; // error! + if (key_length_in <= 1) return 0; // ditto! + + abyte *our_key = (abyte *)key_data; + abyte hashed[4] = { 0, 0, 0, 0 }; + + int key_length = minimum(key_length_in - 1, MAX_STRING_CHARS_USED); + + int fill_posn = 0; + // add the characters from the beginning of the string. + for (int i = 0; i < key_length; i++) { + // add to the primary area. + hashed[fill_posn] = hashed[fill_posn] + our_key[i]; + fill_posn++; + if (fill_posn >= 4) fill_posn = 0; + // add to the secondary area (the next in rotation after primary). + hashed[fill_posn] = hashed[fill_posn] + (our_key[i] / 4); + } + // add the characters from the end of the string. + for (int k = key_length_in - 2; + (k >= 0) && (k >= key_length_in - 1 - key_length); k--) { + // add to the primary area. + hashed[fill_posn] = hashed[fill_posn] + our_key[k]; + fill_posn++; + if (fill_posn >= 4) fill_posn = 0; + // add to the secondary area (the next in rotation after primary). + hashed[fill_posn] = hashed[fill_posn] + (our_key[k] / 4); + } + + basis::un_int to_return = 0; + for (int j = 0; j < 4; j++) to_return = (to_return << 8) + hashed[j]; + return to_return; +} + +////////////// + +hashing_algorithm *astring_hasher::clone() const +{ return new astring_hasher; } + +basis::un_int astring_hasher::hash(const void *key_data, int key_length_in) const +{ + if (!key_data) return 0; // error. + const astring *real_key = (const astring *)key_data; + if (real_key->length() + 1 != key_length_in) { +// printf("differing key lengths, string len=%d, key len=%d\n", +// real_key->length() + 1, key_length_in); + } + return string_hasher().hash((const void *)real_key->observe(), + real_key->length() + 1); +} + +} //namespace. + diff --git a/nucleus/library/structures/string_hasher.h b/nucleus/library/structures/string_hasher.h new file mode 100644 index 00000000..87e45e9b --- /dev/null +++ b/nucleus/library/structures/string_hasher.h @@ -0,0 +1,54 @@ +#ifndef STRING_HASHER_CLASS +#define STRING_HASHER_CLASS + +/*****************************************************************************\ +* * +* Name : string_hasher * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "hash_table.h" + +namespace structures { + +//! Implements a simple hashing algorithm for strings. +/*! This uses a portion of the string's contents to create a hash value. */ + +class string_hasher : public virtual hashing_algorithm +{ +public: + virtual basis::un_int hash(const void *key_data, int key_length) const; + //!< returns a value that can be used to index into a hash table. + /*!< the returned value is loosely based on the "key_data" and the + "key_length" we are provided with. it is expected that the "key_data" + really is a 'char' pointer whose length is "key_length" (including the + zero terminator at the end). */ + + virtual hashing_algorithm *clone() const; + //!< implements cloning of the algorithm object. +}; + +////////////// + +class astring_hasher : public virtual hashing_algorithm +{ +public: + virtual basis::un_int hash(const void *key_data, int key_length) const; + //!< similar to string_hasher, but expects "key_data" as an astring pointer. + + virtual hashing_algorithm *clone() const; + //!< implements cloning of the algorithm object. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/string_table.cpp b/nucleus/library/structures/string_table.cpp new file mode 100644 index 00000000..fb0b7f52 --- /dev/null +++ b/nucleus/library/structures/string_table.cpp @@ -0,0 +1,110 @@ +/*****************************************************************************\ +* * +* Name : string_table * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "object_packers.h" +#include "string_table.h" +#include "symbol_table.h" + +#include +#include + +using namespace basis; + +namespace structures { + +string_table::string_table(const string_table &to_copy) +: symbol_table(to_copy.estimated_elements()), + _add_spaces(false) +{ + *this = to_copy; +} + +string_table::~string_table() {} + +bool string_table::is_comment(const astring &to_check) +{ return to_check.begins(STRTAB_COMMENT_PREFIX); } + +string_table &string_table::operator = (const string_table &to_copy) +{ + if (this == &to_copy) return *this; + (symbol_table &)*this = (const symbol_table &)to_copy; + _add_spaces = to_copy._add_spaces; + return *this; +} + +astring string_table::text_form() const +{ + astring output; + const char *space_char = ""; + if (_add_spaces) space_char = " "; + for (int i = 0; i < symbols(); i++) { + if (is_comment(name(i))) + output += a_sprintf("%s\n", operator[](i).s()); + else + output += a_sprintf("%s%s=%s%s\n", name(i).s(), space_char, + space_char, operator[](i).s()); + } + return output; +} + +bool string_table::operator ==(const string_table &to_compare) const +{ + if (to_compare.symbols() != symbols()) return false; + for (int i = 0; i < symbols(); i++) { + const astring &key = name(i); + astring *str1 = find(key); + astring *str2 = to_compare.find(key); + if (!str2) return false; + if (*str1 != *str2) return false; + } + return true; +} + +int string_table::packed_size() const +{ + int size = sizeof(int); + for (int i = 0; i < symbols(); i++) { + size += name(i).length(); + size += operator[](i).length(); + } + return size; +} + +void string_table::pack(byte_array &packed_form) const +{ + structures::attach(packed_form, symbols()); + for (int i = 0; i < symbols(); i++) { + name(i).pack(packed_form); + operator[](i).pack(packed_form); + } +} + +bool string_table::unpack(byte_array &packed_form) +{ + reset(); + int syms; + if (!structures::detach(packed_form, syms)) return false; + for (int i = 0; i < syms; i++) { + astring name, content; + if (!name.unpack(packed_form)) return false; + if (!content.unpack(packed_form)) return false; + outcome ret = add(name, content); + if (ret != common::IS_NEW) return false; + } + return true; +} + +} //namespace. + + diff --git a/nucleus/library/structures/string_table.h b/nucleus/library/structures/string_table.h new file mode 100644 index 00000000..611c6258 --- /dev/null +++ b/nucleus/library/structures/string_table.h @@ -0,0 +1,82 @@ +#ifndef STRING_TABLE_CLASS +#define STRING_TABLE_CLASS + +/*****************************************************************************\ +* * +* Name : string_table * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "symbol_table.h" + +#include +#include + +namespace structures { + +//! Provides a symbol_table that holds strings as the content. +/*! This is essentially a table of named strings. */ + +class string_table +: public symbol_table, + public virtual basis::packable, + public virtual basis::hoople_standard +{ +public: + string_table(int estimated_elements = 100) : symbol_table(estimated_elements), + _add_spaces(false) {} + //!< the "estimated_elements" specifies how many items to prepare to efficiently hold. + string_table(const string_table &to_copy); + virtual ~string_table(); + + DEFINE_CLASS_NAME("string_table"); + + string_table &operator = (const string_table &to_copy); + + bool operator ==(const string_table &to_compare) const; + + virtual bool equal_to(const equalizable &to_compare) const { + const string_table *cast = dynamic_cast(&to_compare); + if (!cast) return false; + return operator ==(*cast); + } + + #define STRTAB_COMMENT_PREFIX "#comment#" + //!< anything beginning with this is considered a comment. + /*!< a numerical uniquifier should be appended to the string to ensure that + multiple comments can be handled per table. */ + + static bool is_comment(const basis::astring &to_check); + + basis::astring text_form() const; + //!< prints the contents of the table into the returned string. + /*!< if names in the table start with the comment prefix (see above), then + they will not be printed as "X=Y" but instead as just "Y". */ + + virtual void text_form(basis::base_string &fill) const { fill = text_form(); } + + // dictates whether the output will have spaces between the assignment + // character and the key name and value. default is to not add them. + bool add_spaces() const { return _add_spaces; } + void add_spaces(bool add_them) { _add_spaces = add_them; } + + virtual int packed_size() const; + virtual void pack(basis::byte_array &packed_form) const; + virtual bool unpack(basis::byte_array &packed_form); + +private: + bool _add_spaces; // records whether we add spaces around the assignment. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/symbol_table.h b/nucleus/library/structures/symbol_table.h new file mode 100644 index 00000000..ee4bc246 --- /dev/null +++ b/nucleus/library/structures/symbol_table.h @@ -0,0 +1,467 @@ +#ifndef SYMBOL_TABLE_CLASS +#define SYMBOL_TABLE_CLASS + +/*****************************************************************************\ +* * +* Name : symbol_table * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "pointer_hash.h" +#include "string_hash.h" +#include "symbol_table.h" + +#include +#include +#include + +namespace structures { + +template class internal_symbol_indexer; +template class internal_symbol_info; +template class internal_symbol_list; + +//! Maintains a list of names, where each name has a type and some contents. + +template +class symbol_table +{ +public: + //! constructs a symbol table with sufficient size for "estimated_elements". + /*! the "estimated_elements" dictates how large the symbol table's key space is. the + number of keys that can be stored without collisions (assuming perfect distribution + from the hash function) will be close to the number of elements specified. */ + symbol_table(int estimated_elements = 100); + + symbol_table(const symbol_table &to_copy); + + ~symbol_table(); + + int symbols() const; + //!< returns the number of symbols listed in the table. + + int estimated_elements() const; + //!< returns the number of symbols the table is optimized for. + + void rehash(int estimated_elements); + //!< resizes underlying table to support "estimated_elements". + + void hash_appropriately(int estimated_elements); + //!< Resizes the number of table slots to have space for "estimated_elements". + + symbol_table &operator =(const symbol_table &to_copy); + + basis::outcome add(const basis::astring &name, const contents &storage); + //!< Enters a symbol name into the table along with some contents. + /*!< If the name already exists in the table, then the previous contents + are replaced with these, but EXISTING is returned. If this is a new entry, + then IS_NEW is returned instead. */ + + const basis::astring &name(int index) const; + //!< returns the name held at the "index". + /*!< if the index is invalid, then a bogus name is returned. */ + + void names(string_set &to_fill) const; + //!< returns the names of all the symbols currently held. + + contents &operator [] (int index); + //!< provides access to the symbol_table's contents at the "index". + const contents &operator [] (int index) const; + //!< provides a constant peek at the contents at the "index". + + const contents &get(int index) const { return operator[](index); } + //!< named equivalent for the bracket operator. + contents &use(int index) { return operator[](index); } + //!< named equivalent for the bracket operator. + + contents *find(const basis::astring &name) const; + //!< returns the contents held for "name" or NIL if it wasn't found. + + contents *find(const basis::astring &name, + basis::string_comparator_function *comparator) const; + //!< Specialized search via a comparison method "comparator". + /*!< Searches for a symbol by its "name" but uses a special comparison + function "comparator" to determine if the name is really equal. This + method is by its nature slower than the main find method, since all buckets + must be searched until a match is found. It is just intended to provide + extensibility. */ + + int dep_find(const basis::astring &name) const; + //!< Searches for a symbol by its "name". + /*!< Returns the index or NOT_FOUND. NOTE: this is deprecated; it is far + faster to use the first find method above. */ + + //! Locates the symbol at position "index" and stores it to the parameters. + /*! retrieve() accesses the "index"th symbol in the table and returns the + held contents for it as well as its name. if the outcome is OKAY, then + the returned information is valid. otherwise, the search failed. */ + basis::outcome retrieve(int index, basis::astring &symbol_name, contents &contains) const; + + basis::outcome whack(const basis::astring &name); + //!< removes a symbol from the table. + /*!< this succeeds only if the symbol name is present in the table. */ + + basis::outcome zap_index(int index); + //!< zaps the entry at the specified index. slower than whack(). + + void reset(); // *_symbol_list; //!< our table of symbols. + internal_symbol_indexer *_tracker; //!< indexed lookup support. + + internal_symbol_info *get_index(int index) const; + //!< returns the info item at "index" or NIL. +}; + +////////////// + +template +bool symbol_table_compare(const symbol_table &a, + const symbol_table &b); + //!< returns true if table "a" and table "b" have the same contents. + +////////////// + +// implementations below... + +//#define DEBUG_SYMBOL_TABLE + // uncomment for noisier debugging. + +#ifdef DEBUG_SYMBOL_TABLE + #undef LOG + #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) +#endif + +////////////// + +// this structure keeps track of our symbol table elements. + +template +class internal_symbol_info +{ +public: + contents _content; // the object provided by the user. + basis::astring _name; // symbol's name. + + internal_symbol_info(const contents &content, const basis::astring &name) + : _content(content), _name(name) {} +}; + +////////////// + +// this is our tracking system that allows us to offer array like services. +// each symbol held has an address as one of our wrappers. thus we can +// list those addresses in a hash table along with listing the contents +// in the main hash table. the pointer_hash gives us a list of ids set, so +// we can have some ordering for the items in the table. + +template +class internal_pointer_hider +{ +public: + internal_symbol_info *_held; + + internal_pointer_hider(internal_symbol_info *held) : _held(held) {} +}; + +template +class internal_symbol_indexer +: public pointer_hash > +{ +public: + internal_symbol_indexer(int elems) + : pointer_hash >(elems) {} +}; + +////////////// + +// this object maintains the symbol table's contents. + +template +class internal_symbol_list +: public string_hash > +{ +public: + internal_symbol_list(int elems) + : string_hash >(elems) {} +}; + +////////////// + +// the main algorithms class implementing the external interface. + +#undef static_class_name +#define static_class_name() "symbol_table" + +template +symbol_table::symbol_table(int estimated_elements) +: _symbol_list(new internal_symbol_list(estimated_elements)), + _tracker(new internal_symbol_indexer(estimated_elements)) +{} + +template +symbol_table::symbol_table(const symbol_table &to_copy) +: _symbol_list(new internal_symbol_list + (to_copy._symbol_list->estimated_elements())), + _tracker(new internal_symbol_indexer(to_copy.estimated_elements())) +{ *this = to_copy; } + +template +symbol_table::~symbol_table() +{ + WHACK(_symbol_list); + WHACK(_tracker); +} + +template +int symbol_table::estimated_elements() const +{ return _symbol_list->estimated_elements(); } + +template +void symbol_table::rehash(int estimated_elements) +{ + _symbol_list->rehash(estimated_elements); + _tracker->rehash(estimated_elements); +} + +template +void symbol_table::hash_appropriately(int new_elements) +{ rehash(new_elements); } + +template +int symbol_table::symbols() const +{ return _tracker->ids().elements(); } + +template +symbol_table &symbol_table:: + operator =(const symbol_table &to_copy) +{ + if (this == &to_copy) return *this; + reset(); + for (int i = 0; i < to_copy.symbols(); i++) { + internal_symbol_info *info = to_copy.get_index(i); + if (info) { + internal_symbol_info *new_info + = new internal_symbol_info(*info); + _symbol_list->add(info->_name, new_info); + internal_pointer_hider *new_track = + new internal_pointer_hider(new_info); + _tracker->add(new_info, new_track); + } + } + return *this; +} + +template +void symbol_table::reset() +{ + _symbol_list->reset(); + _tracker->reset(); +} + +template +const basis::astring &symbol_table::name(int index) const +{ + bounds_return(index, 0, symbols() - 1, basis::bogonic()); + return get_index(index)->_name; +} + +template +void symbol_table::names(string_set &to_fill) const +{ + to_fill.reset(); + for (int i = 0; i < symbols(); i++) + to_fill += get_index(i)->_name; +} + +template +internal_symbol_info *symbol_table:: + get_index(int index) const +{ + bounds_return(index, 0, symbols() - 1, NIL); + return _tracker->find(_tracker->ids()[index])->_held; +} + +template +const contents &symbol_table::operator [] (int index) const +{ + bounds_return(index, 0, symbols() - 1, basis::bogonic()); + internal_symbol_info *found = get_index(index); + if (!found) return basis::bogonic(); + return found->_content; +} + +template +contents &symbol_table::operator [] (int index) +{ + bounds_return(index, 0, symbols() - 1, basis::bogonic()); + internal_symbol_info *found = get_index(index); + if (!found) return basis::bogonic(); + return found->_content; +} + +template +contents *symbol_table::find(const basis::astring &name) const +{ +// FUNCDEF("find [name]"); + internal_symbol_info *found = _symbol_list->find(name); + if (!found) return NIL; + return &found->_content; +} + +template +int symbol_table::dep_find(const basis::astring &name) const +{ + internal_symbol_info *entry = _symbol_list->find(name); + if (!entry) return basis::common::NOT_FOUND; + + for (int i = 0; i < symbols(); i++) { + if (_tracker->ids()[i] == entry) return i; + } + return basis::common::NOT_FOUND; // this is bad; it should have been found. +} + +template +struct sym_tab_apply_data +{ + basis::string_comparator_function *_scf; + contents *_found; + basis::astring _to_find; + + sym_tab_apply_data() : _scf(NIL), _found(NIL) {} +}; + +template +bool sym_tab_finder_apply(const basis::astring &key, contents ¤t, + void *data_link) +{ +#ifdef DEBUG_SYMBOL_TABLE + FUNCDEF("sym_tab_finder_apply"); +#endif + sym_tab_apply_data *package + = (sym_tab_apply_data *)data_link; +#ifdef DEBUG_SYMBOL_TABLE + LOG(basis::astring(" checking ") + key); +#endif + bool equals = package->_scf(key, package->_to_find); + if (equals) { + package->_found = ¤t; + return false; // done. + } + return true; // keep going. +} + +template +contents *symbol_table::find(const basis::astring &name, + basis::string_comparator_function *comparator) const +{ +#ifdef DEBUG_SYMBOL_TABLE + FUNCDEF("find [comparator]"); +#endif + if (!comparator) return find(name); // devolve to simplified call. +#ifdef DEBUG_SYMBOL_TABLE + LOG(basis::astring("looking for ") + name); +#endif + sym_tab_apply_data pack; + pack._to_find = name; + pack._scf = comparator; + // iterate across all the items in the hash. + _symbol_list->apply(sym_tab_finder_apply, &pack); + return pack._found; +} + +template +basis::outcome symbol_table::add(const basis::astring &name, const contents &to_add) +{ +// FUNCDEF("add"); + internal_symbol_info *found = _symbol_list->find(name); + if (!found) { + // not there already. + internal_symbol_info *new_item + = new internal_symbol_info(to_add, name); + _symbol_list->add(name, new_item); + internal_pointer_hider *new_track = + new internal_pointer_hider(new_item); + _tracker->add(new_item, new_track); + return basis::common::IS_NEW; + } + // overwrite the existing contents. + found->_content = to_add; + return basis::common::EXISTING; +} + +template +basis::outcome symbol_table::zap_index(int index) +{ + basis::astring dead_name = name(index); + return whack(dead_name); +} + +template +basis::outcome symbol_table::whack(const basis::astring &name) +{ +// FUNCDEF("whack"); + internal_symbol_info *sep_ind = _symbol_list->find(name); + // we need this pointer so we can find the tracker entry easily. + bool found_it = _symbol_list->zap(name); + if (found_it) { + _tracker->zap(sep_ind); + } + return found_it? basis::common::OKAY : basis::common::NOT_FOUND; +} + +template +basis::outcome symbol_table::retrieve(int index, basis::astring &name, + contents &got) const +{ + bounds_return(index, 0, symbols() - 1, basis::common::NOT_FOUND); + internal_symbol_info *found = get_index(index); + name = found->_name; + got = found->_content; + return basis::common::OKAY; +} + +////////////// + +template +bool symbol_table_compare(const symbol_table &a, + const symbol_table &b) +{ +// FUNCDEF("symbol_table_compare"); + + string_set names_a; + a.names(names_a); + string_set names_b; + b.names(names_b); + if (names_a != names_b) return false; + + for (int i = 0; i < names_a.elements(); i++) { + const basis::astring ¤t_key = names_a[i]; + const contents *a_value = a.find(current_key); + const contents *b_value = b.find(current_key); + if (!a_value || !b_value) continue; // not good. + if (*a_value != *b_value) return false; + } + return true; +} + +#ifdef DEBUG_SYMBOL_TABLE + #undef LOG +#endif + +#undef static_class_name + +} //namespace. + +#endif + + diff --git a/nucleus/library/structures/unique_id.h b/nucleus/library/structures/unique_id.h new file mode 100644 index 00000000..43f87183 --- /dev/null +++ b/nucleus/library/structures/unique_id.h @@ -0,0 +1,111 @@ +#ifndef UNIQUE_ID_CLASS +#define UNIQUE_ID_CLASS + +/*****************************************************************************\ +* * +* Name : unique_id * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1999-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace structures { + +//! Provides an abstraction for the responsibilities of a unique identifier. +/*! + These are generally used as a way of masking the underlying ID object while + providing some equality comparisons. It is especially useful when the + underlying object is not itself an object, but just a simple type. +*/ + +template +class unique_id : public virtual basis::equalizable +{ +public: + unique_id(uniquifier initial_value) : _id(initial_value) {} + //!< Constructs a unique id from the "initial_value". + + unique_id(const unique_id &to_copy) { *this = to_copy; } + //!< Constructs a unique id as a copy of the "to_copy" object. + + ~unique_id() {} + + virtual bool equal_to(const equalizable &to_compare) const { + const unique_id *cast = dynamic_cast *>(&to_compare); + if (!cast) throw "error: unique_id::==: unknown type"; + return cast->_id == _id; + } + + //! Returns true if the held id is the same as "to_compare". + /*! The templated uniquifying type absolutely must provide an equality + operator (==) and an assignment operator (=). */ + bool operator == (const unique_id &to_compare) const + { return _id == to_compare._id; } + + //! Sets this id to be the same as "to_copy". + unique_id & operator = (const unique_id &to_copy) + { if (this != &to_copy) _id = to_copy._id; return *this; } + + uniquifier raw_id() const { return _id; } + //!< Returns the held identifier in its native form. + + void set_raw_id(uniquifier new_value) { _id = new_value; } + //!< Sets the held identifier to "new_value". + +private: + uniquifier _id; //!< the held object in its native form. +}; + +////////////// + +//! A unique identifier class that supports sorting. +/*! + The orderable version can be compared for magnitude and permits sorting + the ids based on the underlying type. The underlying type must implement + at least the less than operator. +*/ + +template +class orderable_unique_id : public unique_id +{ +public: + orderable_unique_id(const uniquifier &initial_value) + : unique_id(initial_value) {} + orderable_unique_id(const unique_id &initial_value) + : unique_id(initial_value) {} + ~orderable_unique_id() {} + + /*! the "uniquifier" type absolutely must provide a less than operator (<) + and it must meet the requirements of the "unique_id" template. */ + bool operator < (const unique_id &to_compare) const + { return this->raw_id() < to_compare.raw_id(); } +}; + +////////////// + +//! A unique identifier based on integers. + +class unique_int : public unique_id +{ +public: + unique_int(int initial = 0) : unique_id(initial) {} + //!< implicit default for "initial" of zero indicates bogus id. + + bool operator ! () const { return raw_id() == 0; } + //!< provides a way to test whether an id is valid. + /*!< This uses the implicit assumption that a zero id is invalid or + unassigned. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/structures/version_record.cpp b/nucleus/library/structures/version_record.cpp new file mode 100644 index 00000000..f3bfc659 --- /dev/null +++ b/nucleus/library/structures/version_record.cpp @@ -0,0 +1,269 @@ +/*****************************************************************************\ +* * +* Name : version structures: version, version_record * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "string_array.h" +#include "version_record.h" + +#include + +#include + +using namespace basis; + +namespace structures { + +void *version::__global_module_handle() +{ + static void *__mod_hand = 0; + return __mod_hand; +} + +version::version() +: _components(new string_array) +{ + set_component(MAJOR, "0"); + set_component(MINOR, "0"); + set_component(REVISION, "0"); + set_component(BUILD, "0"); +} + +version::version(const string_array &version_info) +: _components(new string_array(version_info)) +{ + for (int i = 0; i < _components->length(); i++) { + if (!(*_components)[i]) { + // this component is blank; replace it with a zero. + (*_components)[i] = "0"; + } + } +} + +version::version(const astring &formatted_string) +: _components(new string_array) +{ + astring verstring = formatted_string; + // scan through and replace bogus bits with reasonable bits. + for (int j = 0; j < verstring.length(); j++) { + if (verstring[j] == ',') verstring[j] = '.'; + // replace commas with periods. + } + // locate the pieces of the version string. + for (int i = 0; i < verstring.length(); i++) { + int perindy = verstring.find('.', i); + if (negative(perindy)) { + if (verstring.length() - i > 0) { + // add any extra bits after the last period in the string. + *_components += verstring.substring(i, verstring.end()); + } + break; + } + // we found a period, so include anything between the current position and + // that as a component. + *_components += verstring.substring(i, perindy - 1); + i = perindy; + // set i to be at the next period; it will be incremented past that. + } +} + +version::version(int maj, int min, int rev, int build) +: _components(new string_array) +{ + *this = version(a_sprintf("%u.%u.%u.%u", basis::un_int(maj), basis::un_int(min), basis::un_int(rev), + basis::un_int(build))); +} + +version::version(const version &to_copy) +: _components(new string_array) +{ *this = to_copy; } + +version::~version() { WHACK(_components); } + +version &version::operator =(const version &to_copy) +{ + if (this != &to_copy) *_components = *to_copy._components; + return *this; +} + +astring version::text_form() const { return flex_text_form(); } + +int version::components() const { return _components->length(); } + +int version::v_major() const +{ return int(get_component(MAJOR).convert(0)); } + +int version::v_minor() const +{ return int(get_component(MINOR).convert(0)); } + +int version::v_revision() const +{ return int(get_component(REVISION).convert(0)); } + +int version::v_build() const +{ return int(get_component(BUILD).convert(0)); } + +astring version::get_component(int index) const +{ + bounds_return(index, 0, _components->length() - 1, "0"); + return (*_components)[index]; +} + +void version::set_component(int index, const astring &to_set) +{ + if (_components->length() <= index) + _components->resize(index + 1); + if (to_set.t()) + (*_components)[index] = to_set; + else + (*_components)[index] = "0"; +} + +bool version::equal_to(const equalizable &to_test) const +{ + const version *cast = cast_or_throw(to_test, *this); + return *_components == *cast->_components; +} + +bool version::less_than(const orderable &to_test) const +{ + const version *cast = cast_or_throw(to_test, *this); + if (v_major() < cast->v_major()) return true; + else if (v_major() > cast->v_major()) return false; + if (v_minor() < cast->v_minor()) return true; + else if (v_minor() > cast->v_minor()) return false; + if (v_revision() < cast->v_revision()) return true; + else if (v_revision() > cast->v_revision()) return false; + if (v_build() < cast->v_build()) return true; + return false; +} + +bool version::compatible(const version &to_test) const +{ +//fix to be more general + return (v_major() == to_test.v_major()) + && (v_minor() == to_test.v_minor()) + && (v_revision() == to_test.v_revision()); +} + +bool version::bogus() const +{ return !v_major() && !v_minor() && !v_revision() && !v_build(); } + +#define SEPARATE \ + if (style != DOTS) to_return += ", "; \ + else to_return += "." + +astring version::flex_text_form(version_style style, int where) const +{ + // note: the conversions below are to ensure proper treatment of the + // size of the int16 types by win32. + astring to_return; + + if (style == DETAILED) to_return += "major "; + astring temp = get_component(MAJOR); + if (!temp) temp = "0"; + to_return += temp; + if (where == MAJOR) return to_return; + SEPARATE; + + if (style == DETAILED) to_return += "minor "; + temp = get_component(MINOR); + if (!temp) temp = "0"; + to_return += temp; + if (where == MINOR) return to_return; + SEPARATE; + + if (style == DETAILED) to_return += "revision "; + temp = get_component(REVISION); + if (!temp) temp = "0"; + to_return += temp; + if (where == REVISION) return to_return; + SEPARATE; + + if (style == DETAILED) to_return += "build "; + temp = get_component(BUILD); + if (!temp) temp = "0"; + to_return += temp; + + // other components don't have handy names. + if (where > BUILD) { + for (int i = BUILD + 1; i < where; i++) { + SEPARATE; + if (style == DETAILED) to_return += a_sprintf("part_%d ", i + 1); + temp = get_component(i); + if (!temp) temp = "0"; + to_return += temp; + } + } + + return to_return; +} + +version version::from_text(const astring &to_convert) +{ return version(to_convert); } + +int version::packed_size() const +{ + return _components->packed_size(); +} + +void version::pack(byte_array &target) const +{ _components->pack(target); } + +bool version::unpack(byte_array &source) +{ + if (!_components->unpack(source)) return false; + return true; +} + +////////////// + +#define VR_NEWLINE to_return += "\n" + +version_record::~version_record() +{} + +astring version_record::text_form() const +{ + astring to_return; + to_return += "Description: "; + to_return += description; + VR_NEWLINE; + to_return += "File Version: "; + to_return += file_version.text_form(); + VR_NEWLINE; + to_return += "Internal Name: "; + to_return += internal_name; + VR_NEWLINE; + to_return += "Original Name: "; + to_return += original_name; + VR_NEWLINE; + to_return += "Product Name: "; + to_return += product_name; + VR_NEWLINE; + to_return += "Product Version: "; + to_return += product_version.text_form(); + VR_NEWLINE; + to_return += "Company Name: "; + to_return += company_name; + VR_NEWLINE; + to_return += "Copyright: "; + to_return += copyright; + VR_NEWLINE; + to_return += "Trademarks: "; + to_return += trademarks; + VR_NEWLINE; + + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/structures/version_record.h b/nucleus/library/structures/version_record.h new file mode 100644 index 00000000..55ffed35 --- /dev/null +++ b/nucleus/library/structures/version_record.h @@ -0,0 +1,218 @@ +#ifndef VERSION_STRUCTURE_GROUP +#define VERSION_STRUCTURE_GROUP + +/*****************************************************************************\ +* * +* Name : version & version_record * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +/*! @file version_record.h + Note that this header is tuned for forward declaration of the version + and version_record objects; this is a better approach than including this + somewhat heavy header file in other header files. +*/ + +#include "string_array.h" + +#include +#include + +namespace structures { + +//! Holds a file's version identifier. +/*! + The version structures can be used in any of our components because + they're not platform specific. They maintain information about a file + that is part of the released product. +*/ + +class version : public virtual basis::packable, public virtual basis::orderable +{ +public: + version(); //!< constructs a blank version. + + version(const structures::string_array &version_info); + //!< constructs a version from a list of strings that form the components. + /*!< note that if a component is an empty string, it is changed to be a + zero ("0"). */ + + version(const basis::astring &formatted_string); + //!< the version components will be parsed from the "formatted_string". + /*!< the version component separator is the period ('.') or the comma (',') + character. */ + + version(int major, int minor, int rev = 0, int build = 0); + //!< constructs a win32 style version structure from the information. + + version(const version &to_copy); + //!< constructs a copy of "to_copy". + + virtual ~version(); + + version &operator =(const version &to_copy); + //!< assigns this to the "to_copy". + + DEFINE_CLASS_NAME("version"); + + virtual basis::astring text_form() const; + + bool equal_to(const equalizable &to_test) const; + //!< compares two versions for exact equality. + /*!< to perform a check of win32 build compatibility, use the + compatible() method. */ + + bool less_than(const orderable &to_test) const; + //!< reports if this version is less than "to_test". + /*!< supplies the other operator needed for the full set of comparisons + (besides equality). the basis namespace provides templates for the rest + of the comparison operators in . */ + + int components() const; + //!< reports the number of components that make up this version. + + basis::astring get_component(int index) const; + //!< returns the component at the specified index. + /*!< note that if an entry is an empty string, then a string with zero + in it is returned ("0"). */ + + void set_component(int index, const basis::astring &to_set); + //!< sets the component at "index" to "to_set". + /*!< an empty string for "to_set" is turned into a zero. */ + + enum version_places { MAJOR, MINOR, REVISION, BUILD }; + //!< these are names for the first four components of the version. + /*!< there may be more components than four on some platforms. */ + + enum version_style { DOTS, COMMAS, DETAILED }; + //!< different ways that a version object can be displayed. + /*!< DOTS is in the form "1.2.3.4" + COMMAS is in the form "1, 2, 3, 4" + DETAILED is in the form "major 1, minor 2, ..." + */ + + basis::astring flex_text_form(version_style style = DOTS, int including = -1) const; + //!< returns a textual form of the version number. + /*!< the place passed in "including" specifies how much of the version + to print, where a negative number means all components. for example, if + "including" is MINOR, then only the first two components (major and minor + components) are printed. */ + + static version from_text(const basis::astring &to_convert); + //!< returns a version structure parsed from "to_convert". + + virtual int packed_size() const; + virtual void pack(basis::byte_array &target) const; + virtual bool unpack(basis::byte_array &source); + + ////////////// + + // the following methods mainly help on win32 platforms, where the version + // components are always simple short integers. there are only ever four + // of these. if one adheres to that same scheme on other platforms, then + // these functions may be helpful. otherwise, with the mixed alphanumeric + // versions one sees on unix, these are not so great. + + int v_major() const; + //!< major version number. + /*!< major & minor are the most significant values for a numerical version. + these are the familiar numbers often quoted for software products, like + "jubware version 8.2". */ + int v_minor() const; + //!< minor version number. + int v_revision() const; + //!< revision level. + /*!< in the hoople code and the clam system, this number is changed for + every new build. when two versions of a file are the same in major, + minor and revision numbers, then they are said to be compatible. for + those using this version scheme, it asserts that dll compatibility has not + been broken if one swaps those two files in an installation. after the + swap, any components that are dependent on the dll must all link properly + against the replacement file. when in doubt, increment the version number. + some folks automatically increment the revision level every week. */ + int v_build() const; + //!< build number. + /*!< this number is not considered important when comparing file + compatibility. the compatible() method always returns true if two files + differ only in the "build" number (rather than major, minor or revision). + this allows patches to be created with a newer (larger) build number, but + still link fine with existing dlls. since the file is distinguished by + more than just its time stamp, it allows changes to an installation to be + tracked very precisely. some folks keep a catalog of patched components + for each software release and index the patch details by the different + build numbers. */ + + bool compatible(const version &that) const; + //!< returns true if this is compatible with "that" version on win32. + /*!< that means that all version components are the same except for the + last one, the build number. we allow the build numbers to fluctuate so + that patched components can be installed without causing version + complaints. */ + + bool bogus() const; + //!< returns true if the version held here is clearly bogus. + /*!< this means that all four numbers are zero. */ + +////////////// + + static void *__global_module_handle(); + //!< a static resource required to identify the actual win32 module that this lives in. + /*!< this handle is stored statically when the libraries are started up. it records + the handle of the module they belong to for later use in checking versions. */ + +//hmmm: storage here is still missing! + +private: + structures::string_array *_components; //!< our list of version components. +}; + +////////////// + +//! Holds all information about a file's versioning. +/*! Not all of these fields are meaningful on every platform. */ + +class version_record : public virtual basis::root_object +{ +public: + virtual ~version_record(); + + DEFINE_CLASS_NAME("version_record"); + + basis::astring text_form() const; + // returns a view of the fields in this record. + + // these describe a particular file: + basis::astring description; // the purpose of this file. + version file_version; // the version number for this file. + basis::astring internal_name; // the internal name of the file. + basis::astring original_name; // name of file before possible renamings. + + // these describe the project that the file belongs to: + basis::astring product_name; // the product this file belongs to. + version product_version; // the version of the product. + + // these describe the creators of the file: + basis::astring company_name; // name of the company that created the file. + + // legal matters: + basis::astring copyright; // copyright info for this file. + basis::astring trademarks; // trademarks related to the file. + + // extra pieces not stored in record (yet). + basis::astring web_address; // the location of the company on the web. +}; + +////////////// + +} //namespace. + +#endif + diff --git a/nucleus/library/tests_algorithms/makefile b/nucleus/library/tests_algorithms/makefile new file mode 100644 index 00000000..46f442b3 --- /dev/null +++ b/nucleus/library/tests_algorithms/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = tests_algorithms +TYPE = test +TARGETS = test_sorts.exe +DEFINITIONS += USE_HOOPLE_DLLS +LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ + structures textual timely filesystem structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_algorithms/test_sorts.cpp b/nucleus/library/tests_algorithms/test_sorts.cpp new file mode 100644 index 00000000..fefcb2e5 --- /dev/null +++ b/nucleus/library/tests_algorithms/test_sorts.cpp @@ -0,0 +1,86 @@ +/* +* Name : test_sorts +* Author : Chris Koeritz +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace algorithms; +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +const int MAX_ELEMENTS = 1200; + +const int MAX_VALUE = 28000; + +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger().get(), to_print) + +class test_sorts : virtual public unit_base, virtual public application_shell +{ +public: + test_sorts() : application_shell() {} + DEFINE_CLASS_NAME("test_sorts"); + virtual int execute(); +}; + +int test_sorts::execute() +{ + FUNCDEF("execute"); + + int *list = new int[MAX_ELEMENTS]; + for (int i = 0; i < MAX_ELEMENTS; i++) + list[i] = randomizer().inclusive(0, MAX_VALUE); + +//astring ret; +//for (int i = 0; i < MAX_ELEMENTS; i++) ret += a_sprintf("%d ", list[i]); +//LOG(ret); +//LOG("-------------"); + + // check a normal sort. + shell_sort(list, MAX_ELEMENTS); + int last = -1; + for (int j = 0; j < MAX_ELEMENTS; j++) { + ASSERT_FALSE(list[j] < last, "ordering check - list should be ordered at first check"); + last = list[j]; + } + + // re-randomize the list. + for (int i = 0; i < MAX_ELEMENTS; i++) + list[i] = randomizer().inclusive(0, MAX_VALUE); + + // check a reversed sort. + shell_sort(list, MAX_ELEMENTS, true); + last = MAX_VALUE + 100; // past the maximum we'll include in the list. + for (int j = 0; j < MAX_ELEMENTS; j++) { + ASSERT_FALSE(list[j] > last, "ordering check - list should be ordered at second check"); + last = list[j]; + } + + // clean up now. + delete [] list; + + return final_report(); +} + +HOOPLE_MAIN(test_sorts, ) + diff --git a/nucleus/library/tests_basis/checkup.cpp b/nucleus/library/tests_basis/checkup.cpp new file mode 100644 index 00000000..cf0a53bb --- /dev/null +++ b/nucleus/library/tests_basis/checkup.cpp @@ -0,0 +1,52 @@ +/* +* Name : checkup +* Author : Chris Koeritz +** +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "checkup.h" + +#include +#include + +using namespace basis; +using namespace loggers; +using namespace unit_test; + +namespace system_checkup { + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT testing +#undef static_class_name +#define static_class_name() astring("system_checkup") + +bool check_system_characteristics(unit_base &testing) +{ + FUNCDEF("check_system_characteristics") + // a big assumption is that the size of an unsigned character is just + // one byte. if this is not true, probably many things would break... + int byte_size = sizeof(abyte); + ASSERT_EQUAL(byte_size, 1, "byte size should be 1 byte"); + int int16_size = sizeof(int16); + ASSERT_FALSE( (sizeof(uint16) != int16_size) || (int16_size != 2), + "uint16 size should be 2 bytes"); + int uint_size = sizeof(un_int); +//hmmm: the checks are actually redundant... + ASSERT_FALSE( (uint_size != sizeof(int)) || (uint_size != 4), + "un_int size should be 2 bytes"); + // all tests successfully passed. + return true; +} + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*this) +#undef static_class_name + +} // namespace + diff --git a/nucleus/library/tests_basis/checkup.h b/nucleus/library/tests_basis/checkup.h new file mode 100644 index 00000000..8d2b47c3 --- /dev/null +++ b/nucleus/library/tests_basis/checkup.h @@ -0,0 +1,33 @@ +#ifndef CHECKUP_GROUP +#define CHECKUP_GROUP + +/* +* Name : checkup +* Author : Chris Koeritz +* Purpose: +* Checks that certain critical properties are upheld by the runtime +* environment. This could be invoked at the beginning of a main program to +* check these characteristics once before continuing execution. +** +* Copyright (c) 1990-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include + +namespace system_checkup { + + bool check_system_characteristics(unit_test::unit_base &testing); + // used to verify that this compilation system possesses some desired + // characteristics. true is returned if everything checks out, and false + // is returned if some assumption proves untrue. + +} + +#endif + diff --git a/nucleus/library/tests_basis/makefile b/nucleus/library/tests_basis/makefile new file mode 100644 index 00000000..16165886 --- /dev/null +++ b/nucleus/library/tests_basis/makefile @@ -0,0 +1,14 @@ +include cpp/variables.def + +PROJECT = tests_basis +TYPE = test +SOURCE = checkup.cpp +TARGETS = test_array.exe test_boilerplate.exe test_mutex.exe test_string.exe \ + test_system_preconditions.exe +DEFINITIONS += USE_HOOPLE_DLLS +LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ + structures textual timely filesystem structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_basis/test_array.cpp b/nucleus/library/tests_basis/test_array.cpp new file mode 100644 index 00000000..e7d04a6b --- /dev/null +++ b/nucleus/library/tests_basis/test_array.cpp @@ -0,0 +1,929 @@ +/*****************************************************************************\ +* * +* Name : test_array * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//#define DEBUG_ARRAY + // when this flag is turned on, extra checking will be done in the allocator. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///#include //temp + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace timely; +using namespace unit_test; + +//const float MAX_TEST_DURATION_ms = 28 * SECOND_ms; +const float MAX_TEST_DURATION_ms = 200; + // each of the tests calling on the templated tester will take this long. + +const int MAX_SIMULTANEOUS_OBJECTS = 42; // the maximum arrays tested. + +const int MIN_OBJECT = -30; // the smallest array we'll create. +const int MAX_OBJECT = 98; // the largest array we'll create. + +const int MIN_BLOCK = 100; // the smallest exemplar we'll use. +const int MAX_BLOCK = MAX_OBJECT * 2; // the largest exempler. + +//#define DEBUG_TEST_ARRAY + // uncomment for noisy version. + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +static chaos a_randomizer; + +////////////// + +class test_array : public application_shell, public unit_base +{ +public: + test_array() + : application_shell(), + unit_base() + { +//hmmm: should go into app shell +////SETUP_CONSOLE_LOGGER; +} + DEFINE_CLASS_NAME("test_array"); + virtual int execute(); + void dump_array(array &ar, const char *name); + void test_arrays_of_void_pointer(); + void test_iteration_speed(); + + template + void array_tester(test_array &ta, const contents &formal(bogus), basis::un_short flags); + +}; + +////////////// + +void test_array::dump_array(array &ar, const char *name) +{ +#ifdef DEBUG_TEST_ARRAY + FUNCDEF("dump_array"); + LOG(a_sprintf("array named \"%s\" has:", name)); + for (int i = 0; i < ar.length(); i++) { + LOG(a_sprintf("\t%4d: %d", i, (int)ar[i])); + } +#else + if (ar.length() && name) {} +#endif +} + +void test_array::test_arrays_of_void_pointer() +{ + FUNCDEF("void pointer test"); + const int MAX_VOID_ARRAY = 20; + array argh(MAX_VOID_ARRAY, NIL, byte_array::SIMPLE_COPY + | byte_array::EXPONE | byte_array::FLUSH_INVISIBLE); + array argh2(argh); + ASSERT_EQUAL(argh.length(), MAX_VOID_ARRAY, "check first array length"); + ASSERT_EQUAL(argh2.length(), MAX_VOID_ARRAY, "check copied array length"); + int wrong_counter = 0; + for (int o = 0; o < MAX_VOID_ARRAY; o++) + if (argh[o] != argh2[o]) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "compare array contents"); + + // fill it with values. + int starter; + for (int i = 0; i < MAX_VOID_ARRAY; i++) + argh[i] = (void *)(&starter + i); + dump_array(argh, "first version"); + + // check the values. + wrong_counter = 0; + for (int j = 0; j < MAX_VOID_ARRAY; j++) + if (argh[j] != (void *)(&starter + j) ) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "compare values that were set"); + + // add a constant to the values. + for (int k = 0; k < MAX_VOID_ARRAY; k++) + argh[k] = (void *)((int *)argh[k] + 23); + dump_array(argh, "second version"); + + // check assignment. + argh2 = argh; + wrong_counter = 0; + for (int n = 0; n < MAX_VOID_ARRAY; n++) + if (argh2[n] != (void *)(&starter + n + 23)) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "compare values that were assigned"); + + // now test that values are kept in place after rearrangement. + argh.zap(3, 4); + dump_array(argh, "third version"); + wrong_counter = 0; + for (int l = 0; l < 3; l++) + if (argh[l] != (void *)(&starter + l + 23)) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "zap low values test"); + wrong_counter = 0; + for (int m = 3; m < MAX_VOID_ARRAY - 2; m++) + if (argh[m] != (void *)(&starter + m + 2 + 23)) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "zap high values test"); +//hmmm: add more tests if void pointer arrays seem in question. +} + +////////////// + +static astring blank_string; + +// jethro is a simple object for containment below. +class jethro +{ +public: + jethro(const astring &i = blank_string) : _truck(i) {} + + astring _truck; + + bool operator ==(const jethro &tc) const { return tc._truck == _truck; } +}; + +////////////// + +// the test_content object is an object with proper copy constructor +// and assignment operator that also has deep contents. +class test_content +{ +public: + abyte _q; + astring _ted; + astring _jed; + int_array _ned; + + test_content(abyte q = 3) + : _q(q), _ted("bl"), _jed("orp"), + _ned(12, NIL) +/* _med(3, 2), + _red(2, 4) */ + { + for (int i = 0; i < _ned.length(); i++) + _ned[i] = -i; +/* + for (int r = 0; r < _med.rows(); r++) + for (int c = 0; c < _med.columns(); c++) + _med[r][c] = jethro(astring((r*c) % 256, 1)); + for (int j = 0; j < _red.rows(); j++) + for (int k = 0; k < _red.columns(); k++) + _red[j][k] = j * k; +*/ + } + + bool operator ==(const test_content &tc) const { + if (tc._q != _q) return false; + if (tc._ted != _ted) return false; + if (tc._jed != _jed) return false; + if (tc._ned.length() != _ned.length()) return false; + for (int i = 0; i < _ned.length(); i++) + if (tc._ned[i] != _ned[i]) return false; + +/* + if (tc._med.rows() != _med.rows()) return false; + if (tc._med.columns() != _med.columns()) return false; + for (int c = 0; c < _med.columns(); c++) + for (int r = 0; r < _med.rows(); r++) + if (tc._med.get(r, c) != _med.get(r, c)) return false; + + if (tc._red.rows() != _red.rows()) return false; + if (tc._red.columns() != _red.columns()) return false; + for (int j = 0; j < _red.rows(); j++) + for (int k = 0; k < _red.columns(); k++) + if (tc._red.get(j, k) != _red.get(j, k)) return false; +*/ + + return true; + } + + operator abyte() const { return _q; } +}; + +////////////// + +template +bool compare_arrays(const array &a, const array &b) +{ + if (a.length() != b.length()) return false; + for (int i = 0; i < a.length(); i++) + if (a[i] != b[i]) return false; + return true; +} + +////////////// + +// this is a templated test for arrays, with some requirements. the contents +// object must support a constructor that takes a simple byte, whether +// that's meaningful for the object or not. if your type to test doesn't +// have that, derive a simple object from it, give it that constructor, and +// then it can be used below. the object must also support comparison with +// == and != for this test to be used. it must also provide a conversion +// to integer that returns the value passed to the constructor. + +template +void test_array::array_tester(test_array &ta, const contents &formal(bogus), basis::un_short flags) +{ + FUNCDEF("array_tester"); + // the space that all training for arrays comes from. + contents *junk_space = new contents[MAX_OBJECT + MAX_BLOCK]; + for (int i = 0; i < MAX_OBJECT - 1; i++) + junk_space[i] = contents(a_randomizer.inclusive('a', 'z')); + junk_space[MAX_OBJECT + MAX_BLOCK - 1] = '\0'; + + array *testers[MAX_SIMULTANEOUS_OBJECTS]; + for (int c = 0; c < MAX_SIMULTANEOUS_OBJECTS; c++) { + // set up the initial array guys. + testers[c] = new array(a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT), + NIL, flags); + // copy the randomized junk space into the new object. + for (int i = 0; i < testers[c]->length(); i++) + testers[c]->put(i, junk_space[i]); + } + + // these are the actions we try out with the array during the test. + // the first and last elements must be identical to the first and last + // tests to perform. + enum actions { first, do_train = first, do_size, do_assign, + do_access, do_zap, do_resizer, do_shrink, do_reset, do_indices, + do_concatenating, do_concatenating2, do_subarray, do_insert, + do_overwrite, do_stuff, do_memory_paring, do_snarf, do_flush, + do_observe, last = do_observe }; + + time_stamp exit_time(MAX_TEST_DURATION_ms); + while (time_stamp() < exit_time) { + int index = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); + int choice = a_randomizer.inclusive(first, last); + switch (choice) { + case do_train: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_train")); +#endif + int new_size = a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT); + testers[index]->retrain(new_size, (contents *)junk_space); + int wrong_counter = 0; + for (int i = 0; i < new_size; i++) + if (junk_space[i] != (*testers[index])[i]) wrong_counter++; + ASSERT_EQUAL(wrong_counter, 0, "test contents after retrain"); + break; + } + case do_size: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_size")); +#endif + array old_version = *testers[index]; + bool at_front = bool(a_randomizer.inclusive(0, 1)); + int new_size = a_randomizer.inclusive(MIN_OBJECT, MAX_OBJECT); + bool smaller = new_size < old_version.length(); + int difference = absolute_value(new_size - old_version.length()); + testers[index]->resize(new_size, + at_front? old_version.NEW_AT_BEGINNING + : old_version.NEW_AT_END); + if (!smaller && difference) { + // neuter the contents in the new section so we can compare. this + // space is filled with whatever the object's constructor chooses. + if (at_front) { + for (int i = 0; i < difference; i++) + testers[index]->put(i, 'Q'); + } else { + for (int i = old_version.length(); + i < old_version.length() + difference; i++) + testers[index]->put(i, 'Q'); + } + } + // now compute an equivalent form of what the state should be. + array equivalent(0, NIL, flags); + if (at_front) { + if (smaller) { + equivalent = old_version.subarray(difference, + old_version.length() - 1); + } else { + array blank(difference, NIL, flags); + for (int i = 0; i < blank.length(); i++) + blank[i] = 'Q'; + equivalent = blank + old_version; + } + } else { + if (smaller) { + equivalent = old_version.subarray(0, old_version.length() + - difference - 1); + } else { + array blank(difference, NIL, flags); + for (int i = 0; i < blank.length(); i++) + blank[i] = 'Q'; + equivalent = old_version + blank; + } + } + ASSERT_EQUAL(equivalent.length(), testers[index]->length(), + "check length of resized form"); + ASSERT_TRUE(compare_arrays(*testers[index], equivalent), + "check contents of resized form"); + break; + } + case do_assign: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_assign")); +#endif + array arrh = *testers[index]; // copy old value. + int to_assign = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); + *testers[index] = *testers[to_assign]; + ASSERT_TRUE(compare_arrays(*testers[index], *testers[to_assign]), + "check result of assign copying array"); + *testers[to_assign] = arrh; // recopy contents to new place. + ASSERT_TRUE(compare_arrays(*testers[to_assign], arrh), + "check result of second assign"); + break; + } + case do_access: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_access")); +#endif + int start = a_randomizer.inclusive(0, testers[index]->length()); + int end = a_randomizer.inclusive(0, testers[index]->length()); + flip_increasing(start, end); + array accumulator(0, NIL, flags); + for (int i = start; i < end; i++) { + contents c = contents(a_randomizer.inclusive(1, 255)); + testers[index]->access()[i] = c; + accumulator += c; + } + for (int j = start; j < end; j++) + ASSERT_EQUAL(accumulator[j - start], (*testers[index])[j], + "comparison accessing at index must be equal"); + break; + } + case do_indices: { +#ifndef CATCH_ERRORS // only works if not catching errors. + #ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_indices")); + #endif + // does some tests on bad indices. + contents c1 = testers[index]->operator[](-50); + contents c2 = testers[index]->operator[](-MAX_OBJECT); + bool test = (c1 == c2); + ASSERT_TRUE(test, "invalid values should be the same"); + int tests = a_randomizer.inclusive(100, 500); + for (int i = 0; i < tests; i++) { + int indy = a_randomizer.inclusive(-1000, MAX_OBJECT * 3); + // testing if we can access without explosions. + contents c3 = testers[index]->operator[](indy); + contents c4 = c3; + ASSERT_EQUAL(c3, c4, "for do_indices, values should be the same"); + } +#endif + break; + } + case do_observe: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_observe")); +#endif + // tests contents returned by observe. + int start = a_randomizer.inclusive(0, testers[index]->length()); + int end = a_randomizer.inclusive(0, testers[index]->length()); + flip_increasing(start, end); + double total_1 = 0; + for (int i = start; i < end; i++) + total_1 += testers[index]->observe()[i]; + double total_2 = 0; + for (int j = end - 1; j >= start; j--) + total_2 += testers[index]->observe()[j]; + ASSERT_EQUAL(total_1, total_2, "totals should match up"); + break; + } + case do_resizer: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_resizer")); +#endif + // tests whether the array will reuse space when it should be able to. + array &arrh = *testers[index]; + // fill with known data. + int i; + for (i = 0; i < arrh.length(); i++) + arrh[i] = contents((i + 23) % 256); + // record the old starting point. + const contents *start = arrh.internal_block_start(); + int zap_amount = a_randomizer.inclusive(1, arrh.length() - 1); + // now take out a chunk from the array at the beginning. + arrh.zap(0, zap_amount - 1); + // test the contents. + for (i = 0; i < arrh.length(); i++) + ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), + "the resized form should have same contents after zap"); + // now add back in the space we ate. + arrh.resize(arrh.length() + zap_amount, arrh.NEW_AT_END); + // check the pointer; it should not have changed. + ASSERT_EQUAL(start, arrh.internal_block_start(), + "the resized form should have same start address"); + // test the contents again. they should start at zero and have the + // same zap_amount offset. the stuff past the original point shouldn't + // be tested since we haven't set it. + for (i = 0; i < arrh.length() - zap_amount; i++) + ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), + "the resized form should still have same contents"); + // now a test of all values through the zap_amount. + arrh.zap(0, zap_amount - 1); + for (i = 0; i < zap_amount; i++) { + arrh.resize(arrh.length() + 1, arrh.NEW_AT_END); + ASSERT_EQUAL(start, arrh.internal_block_start(), + "the slowly resized form should have same start address"); + } + // test the contents once more. they should start at zero and have + // double the zap_amount offset. the stuff past the original point + // shouldn't be tested since we haven't set it. + for (i = 0; i < arrh.length() - 2 * zap_amount; i++) + ASSERT_EQUAL(arrh[i], contents((i + 23 + 2 * zap_amount) % 256), + "the slowly resized form should have same contents"); + + // the tests below should be nearly identical to the ones above, but + // they use the NEW_AT_BEGINNING style of copying. + + // fill with known data. + for (i = 0; i < arrh.length(); i++) + arrh[i] = contents((i + 23) % 256); + // record the old starting point. + start = arrh.internal_block_start(); + zap_amount = a_randomizer.inclusive(1, arrh.length() - 1); + // now take out a chunk from the array at the beginning. + arrh.zap(0, zap_amount - 1); + // test the contents. + for (i = 0; i < arrh.length(); i++) + ASSERT_EQUAL(arrh[i], contents((i + 23 + zap_amount) % 256), + "the resized form with known data should have right contents"); + // now add back in the space we ate. + arrh.resize(arrh.length() + zap_amount, + arrh.NEW_AT_BEGINNING); + // check the pointer; it should not have changed. + ASSERT_EQUAL(start, arrh.internal_block_start(), + "the resized form with known data should have same start address"); + // test the contents again. they should start at zap_amount but have + // the same zap_amount offset. the stuff before the original point + // shouldn't be tested since we haven't set it. + for (i = zap_amount; i < arrh.length(); i++) + ASSERT_EQUAL(arrh[i], contents((i + 23) % 256), + "the known data resized form should have same contents"); + // now a test of all values through the zap_amount. + arrh.zap(0, zap_amount - 1); + for (i = 0; i < zap_amount; i++) { + arrh.resize(arrh.length() + 1, arrh.NEW_AT_BEGINNING); + ASSERT_EQUAL(start, arrh.internal_block_start(), + "the known data slowly resized form should have same start address"); + } + // test the contents once more. the zap_amount offset should stay the + // same since we clobbered the place we added originally, then added + // more space in. so we skip the first part still. + for (i = zap_amount; i < arrh.length(); i++) + ASSERT_EQUAL(arrh[i], contents((i + 23) % 256), + "the known data slowly resized form should have same contents"); + break; + } + case do_zap: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_zap")); +#endif + int start; + int end; + bool erroneous = false; + int chose = a_randomizer.inclusive(1, 100); + if (chose <= 90) { + // there's a ninety percent chance we pick a range that's valid. + start = a_randomizer.inclusive(0, testers[index]->length() - 1); + end = a_randomizer.inclusive(0, testers[index]->length() - 1); + } else if (chose <= 95) { + // and a 5 percent chance we pick to choose the zero index as our + // start; this gets at the code for fast zapping. + start = 0; + end = a_randomizer.inclusive(0, testers[index]->length() - 1); + } else { + // and a 5 percent chance we'll allow picking a bad index. the + // actual chance of picking a bad one is less. + erroneous = true; + start = a_randomizer.inclusive(-2, testers[index]->length() + 3); + end = a_randomizer.inclusive(-2, testers[index]->length() + 3); + } + flip_increasing(start, end); + array old_version = *testers[index]; + testers[index]->zap(start, end); + if (!erroneous) { + array old_head = old_version.subarray(0, start - 1); + array old_tail = old_version.subarray(end + 1, + old_version.length() - 1); + array equivalent = old_head + old_tail; + ASSERT_EQUAL(equivalent.length(), testers[index]->length(), + "the zapped form should not have erroneous length"); + ASSERT_TRUE(compare_arrays(*testers[index], equivalent), + "the zapped form should not have erroneous contents"); + } + break; + } + case do_reset: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_reset")); +#endif + int junk_start = a_randomizer.inclusive(MIN_BLOCK, MAX_BLOCK); + int junk_end = a_randomizer.inclusive(MIN_BLOCK, MAX_BLOCK); + flip_increasing(junk_start, junk_end); + int len = junk_end - junk_start + 1; + testers[index]->reset(len, (contents *)&junk_space[junk_start]); + ASSERT_EQUAL(testers[index]->length(), len, "reset should have proper length"); + for (int i = 0; i < len; i++) + ASSERT_EQUAL(testers[index]->get(i), junk_space[junk_start + i], + "reset should copy data"); + break; + } + case do_shrink: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_shrink")); +#endif + array garp = *testers[index]; + testers[index]->shrink(); + int new_diff = testers[index]->internal_real_length() + - testers[index]->length(); + ASSERT_TRUE(compare_arrays(garp, *testers[index]), "shrink should keep contents"); + // now force a real shrinkage. + if (testers[index]->length() < 5) continue; // need some room. + // pick an element to keep that is not first or last. + int kept = a_randomizer.inclusive(1, testers[index]->last() - 1); + // whack all but the lucky element. + testers[index]->zap(kept + 1, testers[index]->last()); + testers[index]->zap(0, kept - 1); + // shrink it again, now a big shrink. + testers[index]->shrink(); + // check the difference now. + new_diff = testers[index]->internal_real_length() + - testers[index]->length(); + ASSERT_FALSE(new_diff > 1, "massive shrink size should be correct"); + + // restore the original contents and block size. + *testers[index] = garp; + break; + } + case do_concatenating: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_concatenating")); +#endif + for (int i = 0; i < a_randomizer.inclusive(1, 20); i++) { + contents new_c = contents(a_randomizer.inclusive('a', 'z')); + testers[index]->concatenate(new_c); + ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), + "value should be equal after concatenate"); + } + int indy = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); + array flirpan = *testers[indy]; + int start = a_randomizer.inclusive(0, flirpan.length() - 1); + int end = a_randomizer.inclusive(0, flirpan.length() - 1); + flip_increasing(start, end); + flirpan = flirpan.subarray(start, end); + array grumzor = *testers[index]; // old copy. + testers[index]->concatenate(flirpan); + array bubula = grumzor + flirpan; + ASSERT_TRUE(compare_arrays(bubula, *testers[index]), + "contents should be correct after concatenate or concatenation"); + contents first_value; + contents second_value; + if (testers[index]->length() >= 1) + first_value = testers[index]->get(0); + if (testers[index]->length() >= 2) + second_value = testers[index]->get(1); + const int max_iters = a_randomizer.inclusive(1, 42); + for (int j = 0; j < max_iters; j++) { + contents new_c = contents(a_randomizer.inclusive('a', 'z')); + *testers[index] = *testers[index] + new_c; + // correct our value checks if new indices became available. + if (testers[index]->length() == 1) { + first_value = testers[index]->get(0); + } else if (testers[index]->length() == 2) { + second_value = testers[index]->get(1); + } + + ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), + "value should not be wrong after concatenation"); + + ASSERT_FALSE((testers[index]->length() >= 1) && (first_value != testers[index]->get(0)), + "first value should not be corrupted"); + ASSERT_FALSE((testers[index]->length() >= 2) && (second_value != testers[index]->get(1)), + "second value should not be corrupted"); + + *testers[index] += new_c; + // we don't have to correct the first value any more, but might have + // to correct the second. + if (testers[index]->length() == 2) { + second_value = testers[index]->get(1); + } + ASSERT_EQUAL(new_c, testers[index]->get(testers[index]->last()), + "value should be right after second concatenation"); + ASSERT_FALSE( (testers[index]->length() >= 1) && (first_value != testers[index]->get(0)), + "first value should be uncorrupted after second concat"); + ASSERT_FALSE((testers[index]->length() >= 2) && (second_value != testers[index]->get(1)), + "second value should be uncorrupted after second concat"); + } + break; + } + case do_concatenating2: { + // this tests the new concatenate content array method for array. +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_concatenating2")); +#endif + array &flirpan = *testers[index]; + int new_len = a_randomizer.inclusive(0, 140); + contents *to_add = new contents[new_len]; + for (int i = 0; i < new_len; i++) { + int rando = a_randomizer.inclusive(0, MAX_BLOCK - 1); + to_add[i] = junk_space[rando]; + } + int old_len = flirpan.length(); + flirpan.concatenate(to_add, new_len); + for (int i = 0; i < new_len; i++) { + ASSERT_EQUAL(flirpan[i + old_len], to_add[i], + "value should not be wrong after content array concatenation"); + } + delete [] to_add; // clean up. + break; + } + case do_subarray: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_subarray")); +#endif + array flirpan = *testers[index]; + int start = a_randomizer.inclusive(0, flirpan.length() - 1); + int end = a_randomizer.inclusive(0, flirpan.length() - 1); + flip_increasing(start, end); + flirpan = flirpan.subarray(start, end); + for (int i = 0; i < end - start; i++) + ASSERT_EQUAL(flirpan[i], testers[index]->get(i + start), + "subarray should produce right array"); + break; + } + case do_memory_paring: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_memory_paring")); +#endif + for (int i = 0; i < MAX_SIMULTANEOUS_OBJECTS; i++) { + // zap extra junk off so we bound the memory usage. + if (testers[i]->length() > MAX_OBJECT) { + testers[i]->zap(MAX_OBJECT, testers[i]->length() - 1); + testers[i]->shrink(); + } + } + break; + } + case do_snarf: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_snarf")); +#endif + array flirpan = *testers[index]; +// int start = a_randomizer.inclusive(0, flirpan.length() - 1); +// int end = a_randomizer.inclusive(0, flirpan.length() - 1); +// flip_increasing(start, end); +// flirpan = flirpan.subarray(start, end); + int rand_index = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); + if (index == rand_index) continue; // skip it; try again later. + array nugwort = *testers[rand_index]; + // perform a swap between two of our arrays. + array temp_hold(0, NIL, flags); + temp_hold.snarf(*testers[index]); + testers[index]->snarf(*testers[rand_index]); + testers[rand_index]->snarf(temp_hold); + // the copies should have flipped places now. check them. + ASSERT_EQUAL(flirpan.length(), testers[rand_index]->length(), + "snarf needs to produce right length at A"); + for (int i = 0; i < flirpan.length(); i++) + ASSERT_EQUAL(flirpan[i], testers[rand_index]->get(i), + "snarf needs to produce right array at A"); + ASSERT_EQUAL(nugwort.length(), testers[index]->length(), + "snarf needs to produce right length at B"); + for (int j = 0; j < nugwort.length(); j++) + ASSERT_EQUAL(nugwort[j], testers[index]->get(j), + "snarf needs to produce right array at B"); + break; + } + case do_flush: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_flush")); +#endif + array flirpan = *testers[index]; + +///fix up it up in it. + +/// flirpan.reset( + + + break; + } + + case do_insert: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_insert")); +#endif + array hold = *testers[index]; + int where = a_randomizer.inclusive(0, hold.last()); + int how_many = a_randomizer.inclusive(0, 25); + testers[index]->insert(where, how_many); + for (int i = 0; i < where; i++) + ASSERT_EQUAL(hold[i], testers[index]->get(i), + "should have good contents on left after insert"); + for (int j = where + how_many; j < testers[index]->length(); j++) + ASSERT_EQUAL(hold[j - how_many], testers[index]->get(j), + "should have good contents on right after insert"); + break; + } + case do_overwrite: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_overwrite")); +#endif + if (!testers[index]->length()) continue; + array hold = *testers[index]; + int index2 = index; + while (index2 == index) + index2 = a_randomizer.inclusive(0, MAX_SIMULTANEOUS_OBJECTS - 1); + array &hold2 = *testers[index2]; + if (!hold2.length()) continue; + int write_indy = a_randomizer.inclusive(0, hold.last()); + int write_len = minimum(hold2.length(), (hold.length() - write_indy)); +// LOG(a_sprintf("len1 = %d len2 = %d wrindy=%d wrlen=%d", hold.length(), hold2.length(), write_indy, write_len)); + outcome ret = testers[index]->overwrite(write_indy, + *testers[index2], write_len); + ASSERT_EQUAL(ret.value(), common::OKAY, + astring("should not have had outcome=") + common::outcome_name(ret)); + for (int i = 0; i < write_indy; i++) + ASSERT_EQUAL(hold[i], testers[index]->get(i), + "should have good contents on left after overwrite"); + for (int j = write_indy; j < write_indy + write_len; j++) + ASSERT_EQUAL(hold2[j - write_indy], testers[index]->get(j), + "should have good contents in middle after overwrite"); + for (int k = write_indy + write_len; k < testers[index]->length(); k++) + ASSERT_EQUAL(hold[k], testers[index]->get(k), + "should have good contents on right after overwrite"); + break; + } + case do_stuff: { +#ifdef DEBUG_TEST_ARRAY + LOG(a_sprintf("do_stuff")); +#endif + array &hold = *testers[index]; + contents burgers[100]; + int stuff_len = a_randomizer.inclusive(0, hold.length()); + stuff_len = minimum(stuff_len, 100); + outcome ret = hold.stuff(stuff_len, burgers); + ASSERT_EQUAL(ret.value(), common::OKAY, + astring("should not have had outcome=") + common::outcome_name(ret)); + for (int i = 0; i < stuff_len; i++) + ASSERT_EQUAL(burgers[i], hold[i], + "should have good contents after stuff"); + break; + } + default: { + ASSERT_FALSE(true, "test cases should have no invalid choices!"); + break; + } + } + } + + // clean up. + delete [] junk_space; + for (int d = 0; d < MAX_SIMULTANEOUS_OBJECTS; d++) delete testers[d]; +} + +////////////// + +struct gerkin { int l; abyte *p; char *r; void *pffttt; }; + +gerkin borgia; + +class foop +{ +public: + virtual ~foop() {} + virtual gerkin *boorba() = 0; +}; + +class boop : public foop +{ +public: + virtual gerkin *boorba() { return &borgia; } +}; + +void test_array::test_iteration_speed() +{ + FUNCDEF("test_iteration_speed"); + const int MAX_CHUNK = 100000; + const int MAX_REPS = 20; + byte_array chunky(MAX_CHUNK); + + { + time_stamp start; + int sum = 0; + for (int j = 0; j < MAX_REPS; j++) { + for (int i = 0; i < MAX_CHUNK; i++) { + sum += chunky[i]; + } + } + int duration = int(time_stamp().value() - start.value()); + + a_sprintf message("iteration over %d elements took %d ms,\t" + "or %f ms per 1000 iters.", + MAX_CHUNK * MAX_REPS, duration, + double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0); +//base_logger &b = program_wide_logger::get(); + LOG(message); + } + + { + time_stamp start; + int sum = 0; + const abyte *head = chunky.observe(); + for (int j = 0; j < MAX_REPS; j++) { + for (int i = 0; i < MAX_CHUNK; i++) { + sum += head[i]; + } + } + int duration = int(time_stamp().value() - start.value()); + + LOG(a_sprintf("less-safe iteration over %d elements took %d ms,\tor %f ms per 1000 iters.", + MAX_CHUNK * MAX_REPS, duration, + double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0)); + } + { + time_stamp start; + boop tester; +// int sum = 0; + for (int j = 0; j < MAX_REPS; j++) { + for (int i = 0; i < MAX_CHUNK; i++) { +// chunky.modus().sampler(); + tester.boorba(); + } + } + int duration = int(time_stamp().value() - start.value()); + + LOG(a_sprintf("virtual-function-only over %d elements took %d ms,\tor %f ms per 1000 iters.", + MAX_CHUNK * MAX_REPS, duration, + double(duration) / double(MAX_CHUNK * MAX_REPS) * 1000.0)); + } +} + +////////////// + +int test_array::execute() +{ + FUNCDEF("execute"); +#if 0 +// if enabled for the abusive type tests, then allow this one... + test_iteration_speed(); +// LOG(a_sprintf("did iteration test.")); +#endif + + int_array checking_start; + ASSERT_FALSE(checking_start.length(), + "int_array should not have contents from empty constructor"); + + double_plus bogus4 = float(12.32); + array_tester(*this, bogus4, + byte_array::SIMPLE_COPY | byte_array::EXPONE + | byte_array::FLUSH_INVISIBLE); +// LOG(a_sprintf("did float array test.")); + + int bogus2 = 39; + array_tester(*this, bogus2, + byte_array::SIMPLE_COPY | byte_array::EXPONE + | byte_array::FLUSH_INVISIBLE); +// LOG(a_sprintf("did int array test.")); + + test_content bogus3(12); + array_tester(*this, bogus3, byte_array::EXPONE + | byte_array::FLUSH_INVISIBLE); +// LOG(a_sprintf("did test_content array test.")); + + test_arrays_of_void_pointer(); +// LOG(a_sprintf("did void * array test.")); + + abyte bogus1 = 'c'; + array_tester(*this, bogus1, + byte_array::SIMPLE_COPY | byte_array::EXPONE + | byte_array::FLUSH_INVISIBLE); +// LOG(a_sprintf("did byte array test.")); + +//// LOG("array:: works for those functions tested."); + + return final_report(); +} + +HOOPLE_MAIN(test_array, ) + diff --git a/nucleus/library/tests_basis/test_boilerplate.cpp b/nucleus/library/tests_basis/test_boilerplate.cpp new file mode 100644 index 00000000..6a2ea798 --- /dev/null +++ b/nucleus/library/tests_basis/test_boilerplate.cpp @@ -0,0 +1,59 @@ +/*****************************************************************************\ +* * +* Name : test_boilerplate * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Puts an object through its pacess--this is intended to provide the basic * +* framework for a unit test using the hoople testing framework. * +* * +******************************************************************************* +* Copyright (c) 2011-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) + +////////////// + +class test_boilerplate : virtual public unit_base, virtual public application_shell +{ +public: + test_boilerplate() : unit_base() {} + DEFINE_CLASS_NAME("test_boilerplate"); + virtual int execute(); +}; + +HOOPLE_MAIN(test_boilerplate, ); + +////////////// + +int test_boilerplate::execute() +{ + FUNCDEF("execute"); +//do some testing + ASSERT_TRUE(true, "true is somehow not true?"); + return final_report(); +} + diff --git a/nucleus/library/tests_basis/test_mutex.cpp b/nucleus/library/tests_basis/test_mutex.cpp new file mode 100644 index 00000000..743e0004 --- /dev/null +++ b/nucleus/library/tests_basis/test_mutex.cpp @@ -0,0 +1,301 @@ +/*****************************************************************************\ +* * +* Name : test_mutex * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace timely; +using namespace processes; +using namespace structures; +using namespace unit_test; + +//#define DEBUG_MUTEX + // uncomment for a verbose test run. + +const int MAX_MUTEX_TIMING_TEST = 2000000; + // the number of times we'll lock and unlock a mutex. + +const int DEFAULT_FISH = 32; + // the number of threads, by default. + +const int DEFAULT_RUN_TIME = 2 * SECOND_ms; + // the length of time to run the program. + +const int THREAD_PAUSE_LOWEST = 0; +const int THREAD_PAUSE_HIGHEST = 48; + // this is the range of random sleeps that a thread will take after + // performing it's actions. + +const int MIN_SAME_THREAD_LOCKING_TESTS = 100; +const int MAX_SAME_THREAD_LOCKING_TESTS = 1000; + // the range of times we'll test recursively locking the mutex. + +int concurrent_biters = 0; + // the number of threads that are currently active. + +int grab_lock = 0; + // this is upped whenever a fish obtains access to the mutex. + +mutex &guard() { static mutex _muttini; return _muttini; } + // the guard ensures that the grab lock isn't molested by two fish at + // once... hopefully. + +astring protected_string; + // this string is protected only by the mutex of guard(). + +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) + // our macro for logging with a timestamp. + +// expects guardian mutex to already be locked once when coming in. +void test_recursive_locking(chaos &_rando) +{ + int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS, + MAX_SAME_THREAD_LOCKING_TESTS); + int locked = 0; + for (int i = 0; i < test_attempts; i++) { + bool lock = !!(_rando.inclusive(0, 1)); + if (lock) { + guard().lock(); + locked++; // one more lock. + } else { + if (locked > 0) { + // must be sure we are not already locally unlocked completely. + guard().unlock(); + locked--; + } + } + } + for (int j = 0; j < locked; j++) { + // drop any locks we had left during the test. + guard().unlock(); + } +} + +//hmmm: how are these threads different so far? they seem to do exactly +// the same thing. maybe one should eat chars from the string. + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT c_testing + +class piranha : public ethread +{ +public: + chaos _rando; // our randomizer. + unit_base &c_testing; // provides for test recording. + + piranha(unit_base &testing) : ethread(0), c_testing(testing) { + FUNCDEF("constructor"); + safe_add(concurrent_biters, 1); + ASSERT_TRUE(concurrent_biters >= 1, "the piranha is very noticeable"); +//LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters)); + } + + virtual ~piranha() { + FUNCDEF("destructor"); + safe_add(concurrent_biters, -1); +//LOG("reached piranha destructor."); + } + + DEFINE_CLASS_NAME("piranha"); + + void perform_activity(void *formal(data)) { + FUNCDEF("perform_activity"); + { + // we grab the lock. + auto_synchronizer locked(guard()); + // in this case, we make use of auto-synchronizer, handily testing it as well. + ASSERT_TRUE(&locked != NIL, "auto_synchronizer should grab the mutex object's lock"); + // this is not a real test, but indicates that we did actually increase the number of + // unit tests by one, since we're using auto_synchronizer now. + safe_add(grab_lock, 1); + ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active"); + protected_string += char(_rando.inclusive('a', 'z')); + + test_recursive_locking(_rando); + + safe_add(grab_lock, -1); + } + // dropped the lock. snooze a bit. + if (!should_stop()) + time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST)); + } + +}; + +class barracuda : public ethread +{ +public: + chaos _rando; // our randomizer. + unit_base &c_testing; // provides for test recording. + + barracuda(unit_base &testing) : ethread(0), c_testing(testing) { + FUNCDEF("constructor"); + safe_add(concurrent_biters, 1); + ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed"); +//LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters)); + } + + virtual ~barracuda() { + FUNCDEF("destructor"); + safe_add(concurrent_biters, -1); +//LOG("reached barracuda destructor."); + } + + DEFINE_CLASS_NAME("barracuda"); + + void perform_activity(void *formal(data)) { + FUNCDEF("perform_activity"); + // we grab the lock. + guard().lock(); + safe_add(grab_lock, 1); + ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active"); + + test_recursive_locking(_rando); + + protected_string += char(_rando.inclusive('a', 'z')); + safe_add(grab_lock, -1); + guard().unlock(); + // done with the lock. sleep for a while. + if (!should_stop()) + time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST)); + } +}; + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*this) + +class test_mutex : virtual public unit_base, virtual public application_shell +{ +public: + chaos _rando; // our randomizer. + + test_mutex() : application_shell() {} + + DEFINE_CLASS_NAME("test_mutex"); + + int execute(); +}; + +int test_mutex::execute() +{ + FUNCDEF("execute"); + + { + // we check how long a lock and unlock of a non-locked mutex will take. + // this is important to know so that we aren't spending much of our time + // locking mutexes just due to the mechanism. + mutex ted; + time_stamp mutt_in; + for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) { + ted.lock(); + ted.unlock(); + } + time_stamp mutt_out; + double run_count = MAX_MUTEX_TIMING_TEST; + double full_run_time = (mutt_out.value() - mutt_in.value()) / SECOND_ms; + double time_per_lock = (mutt_out.value() - mutt_in.value()) / run_count; + log(a_sprintf("%.0f mutex lock & unlock pairs took %.3f seconds,", + run_count, full_run_time)); + log(a_sprintf("or %f ms per (lock+unlock).", time_per_lock)); + ASSERT_TRUE(time_per_lock < 1.0, "mutex lock timing should be super fast"); + } + + // make sure the guard is initialized before the threads run. + guard().lock(); + guard().unlock(); + + amorph thread_list; + + for (int i = 0; i < DEFAULT_FISH; i++) { + ethread *t = NIL; + if (i % 2) t = new piranha(*this); + else t = new barracuda(*this); + thread_list.append(t); + ethread *q = thread_list[thread_list.elements() - 1]; + ASSERT_EQUAL(q, t, "amorph pointer equivalence is required"); + // start the thread we added. + thread_list[thread_list.elements() - 1]->start(NIL); + } + + time_stamp when_to_leave(DEFAULT_RUN_TIME); + while (when_to_leave > time_stamp()) { + time_control::sleep_ms(100); + } + +//hmmm: try just resetting the amorph; +// that should work fine. + +#ifdef DEBUG_MUTEX + LOG("now cancelling all threads...."); +#endif + + for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel(); + +#ifdef DEBUG_MUTEX + LOG("now stopping all threads...."); +#endif + + for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop(); + + int threads_active = 0; + for (int k = 0; k < thread_list.elements(); k++) { + if (thread_list[k]->thread_active()) threads_active++; + } + ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now"); + +#ifdef DEBUG_MUTEX + LOG("resetting thread list...."); +#endif + + thread_list.reset(); // should whack all threads. + + ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now"); + +#ifdef DEBUG_MUTEX + LOG("done exiting from all threads...."); + + LOG(astring(astring::SPRINTF, "the accumulated string had %d characters " + "which means\nthere were %d thread activations from %d threads.", + protected_string.length(), protected_string.length(), + DEFAULT_FISH)); +#endif + + return final_report(); +} + +HOOPLE_MAIN(test_mutex, ) + diff --git a/nucleus/library/tests_basis/test_string.cpp b/nucleus/library/tests_basis/test_string.cpp new file mode 100644 index 00000000..f89318a9 --- /dev/null +++ b/nucleus/library/tests_basis/test_string.cpp @@ -0,0 +1,1279 @@ +/*****************************************************************************\ +* * +* Name : test_string * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//#define DEBUG_STRING + // set this to enable debugging features of the string class. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//HOOPLE_STARTUP_CODE; + +//#define DEBUG_STRING_TEST + // uncomment for testing version. + +const float TEST_RUNTIME_DEFAULT = .02 * MINUTE_ms; + // the test, by default, will run for this long. + +////////////// + +class test_string : public application_shell, public unit_base +{ +public: + test_string() {} + ~test_string() {} + + DEFINE_CLASS_NAME("test_string"); + + virtual int execute(); + + void run_test_01(); + void run_test_02(); + void run_test_03(); + void run_test_04(); + void run_test_05(); + void run_test_06(); + void run_test_07(); + void run_test_08(); + void run_test_09(); + void run_test_10(); + void run_test_11(); + void run_test_12(); + void run_test_13(); + void run_test_14(); + void run_test_15(); + void run_test_16(); + void run_test_17(); + void run_test_18(); + void run_test_19(); + void run_test_20(); + void run_test_21(); + void run_test_22(); + void run_test_23(); + void run_test_24(); + void run_test_25(); + void run_test_26(); + void run_test_27(); + void run_test_28(); + void run_test_29(); + void run_test_30(); + void run_test_31(); + void run_test_32(); + void run_test_33(); + void run_test_34(); + void run_test_35(); + void run_test_36(); + void run_test_37(); + void run_test_38(); + void run_test_39(); + void run_test_40(); + void run_test_41(); + void run_test_42(); +}; + +////////////// + +chaos rando; + +#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) + +#define WHERE __WHERE__.s() + +// test: reports an error if the condition evaluates to non-zero. +#define test(expr) { \ + ASSERT_FALSE(expr, astring("operator test should work: ") + #expr); \ +} + +static basis::astring staticity_test("yo!"); + // used below to check whether static strings are looking right. + +////////////// + +void test_string::run_test_01() +{ + FUNCDEF("run_test_01"); + +// const int TEST_EMPTY = 10000000; +// time_stamp started; +// for (int i = 0; i < TEST_EMPTY; i++) { +// astring glob = astring::empty_string(); +// } +// int duration = int(time_stamp().value() - started.value()); +// LOG(a_sprintf("duration of empty string test=%d ms", duration)); + + // test simple string operations, like construction, length, equality. + astring fred1("hyeargh!"); + astring fred2("matey."); + astring fred3; + fred3 = fred1; + fred3 += fred2; + astring fred4(fred2); + + ASSERT_EQUAL(fred1.length(), int(strlen(fred1.s())), "length should be correct (a)."); + ASSERT_EQUAL(fred2.length(), int(strlen(fred2.s())), "length should be correct (b)."); + ASSERT_EQUAL(fred3.length(), int(strlen(fred3.s())), "length should be correct (c)."); + ASSERT_EQUAL(fred4.length(), int(strlen(fred4.s())), "length should be correct (d)."); + +#ifdef DEBUG_STRING_TEST + LOG("[ " + fred1 + " & " + fred2 + "] -> " + fred3); +#endif + + ASSERT_EQUAL(fred1, astring("hyeargh!"), "failure in comparison (a)."); + ASSERT_EQUAL(fred2, astring("matey."), "failure in comparison (b)."); + ASSERT_EQUAL(fred3, astring("hyeargh!matey."), "failure in comparison (c)."); + ASSERT_EQUAL(fred4, astring("matey."), "failure in comparison (d-1)."); + ASSERT_EQUAL(fred4, fred2, "failure in comparison (d-2)."); + + a_sprintf nullo; + ASSERT_EQUAL(nullo, astring(), "forward blank a_sprintf isn't blank."); + ASSERT_EQUAL(astring(), nullo, "backward blank a_sprintf isn't blank."); + ASSERT_EQUAL(nullo, astring::empty_string(), "forward blank a_sprintf isn't empty."); + ASSERT_EQUAL(astring::empty_string(), nullo, "backward blank a_sprintf isn't empty."); +} + +void test_string::run_test_02() +{ + FUNCDEF("run_test_02"); + // assorted tests involving strings as pointers. + astring *fred1 = new astring("flipper ate"); + astring *fred2 = new astring(" my sandwich."); + astring *fred3 = new astring; + *fred3 = *fred1; + *fred3 += *fred2; + + // testing adding a null to a string. + *fred2 += (char *)NIL; + *fred3 += (char *)NIL; + +#ifdef DEBUG_STRING_TEST + LOG(astring("[ ") + *fred1 + " & " + *fred2 + "] -> " + *fred3); +#endif + + ASSERT_EQUAL(*fred1, astring("flipper ate"), "flipper A failure in comparison"); + ASSERT_EQUAL(*fred2, astring(" my sandwich."), "sandwich A failure in comparison"); + ASSERT_EQUAL(*fred3, astring("flipper ate my sandwich."), "full f-s A failure in comparison"); + delete fred1; + delete fred2; + delete fred3; +} + +void test_string::run_test_03() +{ + FUNCDEF("run_test_03"); + // tests some things about zap. + astring fleermipe("hello my frobious."); + fleermipe.zap(0, fleermipe.length() - 1); + ASSERT_EQUAL(fleermipe.length(), 0, "length not 0 after deleting entire astring"); +} + +void test_string::run_test_04() +{ + FUNCDEF("run_test_04"); + astring test_string("this test string will be chopped up."); +#ifdef DEBUG_STRING_TEST + LOG(astring("original is: ") + test_string); +#endif + astring fred(test_string.s()); + fred.zap(0, fred.find('w')); + +#ifdef DEBUG_STRING_TEST + LOG(astring("now, the one chopped through 'w' is: ") + fred); +#endif + + ASSERT_EQUAL(fred, astring("ill be chopped up."), "first zap failed"); + + astring blorg(test_string); + blorg.zap(blorg.find('p'), blorg.length() - 1); +#ifdef DEBUG_STRING_TEST + LOG(astring("now the one chopped from p to the end: ") + blorg); +#endif + + ASSERT_EQUAL(blorg, astring("this test string will be cho"), "second zap failed"); + + astring fleen; + fleen += test_string; + fleen.zap(7, 14); +#ifdef DEBUG_STRING_TEST + LOG(astring("now the one with 7 through 14 missing: ") + fleen); +#endif + + ASSERT_EQUAL(fleen, astring("this teg will be chopped up."), "third zap failed"); + +#ifdef DEBUG_STRING_TEST + LOG(astring("original astring is now: ") + test_string); +#endif + ASSERT_EQUAL(test_string, astring("this test string will be chopped up."), + "original astring was changed"); +} + +void test_string::run_test_05() +{ + FUNCDEF("run_test_05"); +#ifdef DEBUG_STRING_TEST + LOG("about to test weird things:"); +#endif + astring frieda("glorp"); + astring jorb(frieda); + astring *kleeg = new astring(jorb.s()); + astring plok = frieda; + test(frieda != jorb); + test(jorb != *kleeg); + test(*kleeg != plok); + test(plok != frieda); + astring glorp("glorp"); + test(frieda != glorp); + + WHACK(kleeg); + +#ifdef DEBUG_STRING_TEST + LOG("strings matched up okay."); +#endif + + // test new features sprintf is relying upon. + astring bubba("gumpternations"); + bubba += "_02193"; +#ifdef DEBUG_STRING_TEST + LOG(astring("bubba = ") + bubba); +#endif + ASSERT_EQUAL(bubba, astring("gumpternations_02193"), "+= on char pointer failed."); + + astring bubelah("laksos"); + bubelah += '3'; + bubelah += '8'; + bubelah += '2'; +#ifdef DEBUG_STRING_TEST + LOG(astring("bubelah = ") + bubelah); +#endif + ASSERT_EQUAL(bubelah, astring("laksos382"), "+= on char failed."); + + astring simple_spr0(astring::SPRINTF, "%% yoga splorch %%"); +#ifdef DEBUG_STRING_TEST + LOG(astring("simple sprintf 0 = ") + simple_spr0); +#endif + ASSERT_EQUAL(simple_spr0, astring("% yoga splorch %"), "simple sprintf 0 is wrong"); + astring simple_spr1(astring::SPRINTF, "%d", 23); +#ifdef DEBUG_STRING_TEST + LOG(astring("simple sprintf 1 = ") + simple_spr1); +#endif + ASSERT_EQUAL(simple_spr1, astring("23"), "simple sprintf 1 is wrong"); + astring simple_spr2(astring::SPRINTF, "%s", "yoyo"); +#ifdef DEBUG_STRING_TEST + LOG(astring("simple sprintf 2 = ") + simple_spr2); +#endif + ASSERT_EQUAL(simple_spr2, astring("yoyo"), "simple sprintf 2 is wrong"); + + astring sprintest(astring::SPRINTF, "%s has got me up the %s some %d " + "times, in %p with %d and %lu.", "marge", "ladder", 32, &kleeg, + 812377487L, 213123123L); + astring sprintest2; + sprintest2.reset(astring::SPRINTF, "%s has got me up the %s some %d " + "times, in %p with %d and %lu.", "marge", "ladder", 32, &kleeg, + 812377487L, 213123123L); + astring addr(astring::SPRINTF, "%p", &kleeg); +#ifdef DEBUG_STRING_TEST + LOG("here is your astring sir..."); + LOG(sprintest); + LOG("and addr we will see is..."); + LOG(addr); +#endif + if (sprintest != astring(astring::SPRINTF, "marge has got me up the " + "ladder some 32 times, in %s with 812377487 and 213123123.", addr.s())) + "constructed astring is wrong"; + if (sprintest2 != astring(astring::SPRINTF, "marge has got me up the " + "ladder some 32 times, in %s with 812377487 and 213123123.", addr.s())) + "reset astring is wrong"; +} + +void test_string::run_test_06() +{ + FUNCDEF("run_test_06"); + astring bungee; + bungee += "this astring"; + bungee += " has been constructed gradua"; + bungee += 'l'; + bungee += 'l'; + bungee += 'y'; + astring blorpun(astring::SPRINTF, " out of severa%c", 'l'); + bungee += blorpun; + bungee += " different bits,\nincluding"; + astring freeple(astring::SPRINTF, "%s constructed %s blarg from %f ", " this", "silly", 3.14159265358); + bungee += freeple; + bungee += "radians awa"; + bungee += 'y'; + bungee += '.'; + bungee += "\nhow does it look?\n"; +#ifdef DEBUG_STRING_TEST + LOG(bungee); +#endif + +//MessageBox(0, bungee.s(), "yo, got this", MB_ICONINFORMATION | MB_OK); + ASSERT_EQUAL(bungee, astring("this astring has been constructed gradually out of several different bits,\nincluding this constructed silly blarg from 3.141593 radians away.\nhow does it look?\n"), "constructed astring is wrong"); +} + +void test_string::run_test_07() +{ + FUNCDEF("run_test_07"); + astring a("axes"); + astring b("bakesales"); + astring x("xylophone"); + + test(a >= x); + test(a == b); + test(a > b); + test(b >= x); + test(x <= a); + test(x != x); + test(a != a); +#ifdef DEBUG_STRING_TEST + LOG("comparisons worked"); +#endif +} + +void test_string::run_test_08() +{ + FUNCDEF("run_test_08"); +#ifdef DEBUG_STRING_TEST + LOG("now testing + operator"); +#endif + astring a("fred"); + astring b(" is"); + astring c(" his"); + astring d(" name."); + astring e; + e = a + b + c + d; +#ifdef DEBUG_STRING_TEST + LOG(astring("result is: ") + e); +#endif + astring f; + f = d + c + b + a; +#ifdef DEBUG_STRING_TEST + LOG(astring("reverse is: ") + f); +#endif + astring g; + g = a + c + d + b; +#ifdef DEBUG_STRING_TEST + LOG(astring("tibetan style is: ") + g); +#endif + ASSERT_EQUAL(e, astring("fred is his name."), "astring looks wrong"); + ASSERT_EQUAL(f, astring(" name. his isfred"), "astring looks wrong"); + ASSERT_EQUAL(g, astring("fred his name. is"), "astring looks wrong"); +} + +void test_string::run_test_09() +{ + FUNCDEF("run_test_09"); + astring bleer(astring::SPRINTF, "urghla burgla #%d\n", 23); + char holder[50]; + for (int i = 0; i < bleer.length(); i++) { + bleer.stuff(holder, i); +#ifdef DEBUG_STRING_TEST + LOG(astring(astring::SPRINTF, "%d", i) + " has: " + holder); +#endif + astring my_copy(bleer); + my_copy.zap(i, bleer.length() - 1); + ASSERT_EQUAL(my_copy, astring(holder), "should see no inaccurate stuff() call"); + } +} + +void test_string::run_test_10() +{ + FUNCDEF("run_test_10"); +#ifdef DEBUG_STRING_TEST + LOG("The tenth ones:"); +#endif + astring george("this one will be mangled."); + ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (q)."); + astring tmp1(george.substring(1, 7)); // constructor. + astring tmp2 = george.substring(10, george.length() - 1); // constructor. +#ifdef DEBUG_STRING_TEST + LOG(tmp1 + "... " + tmp2); +#endif + ASSERT_INEQUAL(tmp1, tmp2, "bizarre equality occurred!"); + ASSERT_FALSE( (tmp1 > tmp2) || (tmp2 < tmp1), "bizarre comparison error."); + ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (z)."); +#ifdef DEBUG_STRING_TEST + LOG(george.substring(1, 7)); + LOG("... "); + LOG(george.substring(10, george.length() - 1)); +#endif + ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (a)."); + george.insert(14, "terribly "); + ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (b)."); +#ifdef DEBUG_STRING_TEST + LOG(george); +#endif + astring chunky; + astring mssr_boef_la_tet("eeyoy eye eye capn"); + mssr_boef_la_tet.substring(chunky, 2, 7); + ASSERT_EQUAL(chunky, astring("yoy ey"), "contents wrong after substring"); + + astring fred(george); + ASSERT_TRUE(george.compare(fred, 0, 0, george.length() - 1, true), "did not work"); + ASSERT_TRUE(george.compare(fred, 8, 8, george.length() - 1 - 8, true), "partial did not work"); + + astring taco1("iLikeTacosNSuch"); + astring taco2("iLikeTaCosNSuch"); + ASSERT_TRUE(taco1.compare(taco2, 0, 0, taco1.length() - 1, false), + "tacos case-insensitive compare A did not work"); + ASSERT_FALSE(taco1.compare(taco2, 0, 0, taco1.length() - 1, true), + "tacos case-sensitive compare B did not work"); + + ASSERT_EQUAL(george.length(), int(strlen(george.s())), "length is incorrect (c)."); + george.zap(14, 22); +#ifdef DEBUG_STRING_TEST + LOG(george); +#endif + astring fred_part; + fred_part = fred.substring(0, 13); + ASSERT_EQUAL(fred_part.length(), int(strlen(fred_part.s())), "length incorrect (d)."); + ASSERT_TRUE(george.compare(fred_part, 0, 0, 13, true), "did not work"); + fred_part = fred.substring(23, fred.length() - 1); + ASSERT_EQUAL(fred_part.length(), int(strlen(fred_part.s())), "length incorrect (e)."); + ASSERT_TRUE(george.compare(fred_part, 14, 0, fred_part.length() - 1, true), "did not work"); +#ifdef DEBUG_STRING_TEST + LOG("compares okay"); +#endif +} + +void test_string::run_test_11() +{ + FUNCDEF("run_test_11"); + astring empty; + ASSERT_FALSE(empty.length(), "empty string judged not"); + ASSERT_TRUE(!empty, "not on empty string doesn't work"); + astring non_empty("grungee"); + ASSERT_TRUE(non_empty.length(), "non-empty string judged empty"); + ASSERT_FALSE(!non_empty, "not on non-empty string doesn't work"); +} + +void test_string::run_test_12() +{ + FUNCDEF("run_test_12"); + astring elf0(astring::SPRINTF, "%%_%%_%%"); + ASSERT_FALSE(strcmp(elf0.s(), "%_%_%"), "failed %% printing"); + + char fred[6] = { 'h', 'y', 'a', 'r', 'g', '\0' }; + astring fred_copy(astring::UNTERMINATED, fred, 5); + ASSERT_EQUAL(fred_copy.length(), 5, "length of copy is wrong"); + ASSERT_EQUAL(fred_copy, astring("hyarg"), "length of copy is wrong"); + + char hugh[6] = { 'o', 'y', 'o', 'b', 'o', 'y' }; + astring hugh_copy(astring::UNTERMINATED, hugh, 3); + ASSERT_EQUAL(hugh_copy.length(), 3, "length of copy is wrong"); + ASSERT_EQUAL(hugh_copy, astring("oyo"), "length of copy is wrong"); + + astring another_copy; + another_copy.reset(astring::UNTERMINATED, fred, strlen(fred)); + ASSERT_EQUAL(another_copy.length(), 5, "length of reset copy is wrong"); + ASSERT_EQUAL(another_copy, astring("hyarg"), "length of reset copy is wrong"); +} + +void test_string::run_test_13() +{ +// FUNCDEF("run_test_13"); + // check for possible memory leaks in these combined ops.... 13th. + const astring churg("borjh sjh oiweoklj"); + astring pud = churg; + astring flug = "kase iqk aksjir kljasdo"; + const char *nerf = "ausd qwoeui sof zjh qwei"; + astring snorp = nerf; + pud = "snugbo"; + snorp = flug; + astring pundit("murph"); + pundit = nerf; +} + +void test_string::run_test_14() +{ + FUNCDEF("run_test_14"); + // test the new dynamic sprintf'ing for long strings... 14th. + const int longish_size = 5000; + char temp[longish_size]; + for (int i = 0; i < longish_size; i++) + temp[i] = char(rando.inclusive(1, 255)); + temp[longish_size - 1] = '\0'; + a_sprintf longish("this is a really darned long string of stuff: %s,\nbut doesn't it look interesting?", temp); + // still with us? + longish.zap(longish.length() / 3, 2 * longish.length() / 3); + longish += longish; + ASSERT_EQUAL(longish.length(), int(strlen(longish.s())), "length is incorrect."); +} + +void test_string::run_test_15() +{ + FUNCDEF("run_test_15"); + // test the new dynamic sprintf'ing for long strings... 15th. + int try_1 = 32767; + astring testy(astring::SPRINTF, "%d", try_1); + ASSERT_INEQUAL(testy.convert(95), 95, "default value returned, so it failed."); + ASSERT_EQUAL(testy.convert(95), try_1, "correct value was not returned."); + + long try_2 = 2938754L; + testy = astring(astring::SPRINTF, "%ld", try_2); + ASSERT_INEQUAL((double)testy.convert(98L), (double)98L, "default value returned, so it failed."); + ASSERT_EQUAL((double)testy.convert(98L), (double)try_2, "correct value was not returned."); + + testy = astring(astring::SPRINTF, "%ld", try_2); + ASSERT_INEQUAL((double)testy.convert(98L), (double)98L, "default value returned, so it failed."); + ASSERT_EQUAL((double)testy.convert(98L), (double)try_2, "correct value was not returned."); + + float try_3 = float(2938.754); // weird msvcpp error if don't cast. + testy = astring(astring::SPRINTF, "%f", try_3); + float found = testy.convert(float(98.6)); + ASSERT_INEQUAL(found, 98.6, "default value returned, so it failed."); + ASSERT_EQUAL(found, try_3, "correct value was not returned."); + + { + double try_4 = 25598437.712385; + testy = astring(astring::SPRINTF, "%f", try_4); + double found2 = testy.convert(double(98.6)); + ASSERT_INEQUAL(found2, 98.6, "default value returned, so it failed."); + ASSERT_EQUAL(found2, try_4, "correct value was not returned."); + } + + { + double try_4 = 25598437.712385e38; + testy = astring(astring::SPRINTF, "%f", try_4); + double found2 = testy.convert(double(98.6)); + ASSERT_INEQUAL(found2, 98.6, "default value returned, so it failed."); + ASSERT_EQUAL(found2, try_4, "correct value was not returned."); + } +} + +void test_string::run_test_16() +{ + FUNCDEF("run_test_16"); + // the 16th test group tests boundary checking. + astring gorf("abc"); + for (int i = -3; i < 25; i++) gorf[i] = 't'; + ASSERT_EQUAL(gorf, astring("ttt"), "correct value was not returned (a)."); + ASSERT_EQUAL(gorf.length(), 3, "length is incorrect (a)."); + gorf.insert(3, astring("bru")); + ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (b)."); + ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (b)."); + gorf.insert(35, astring("snu")); + ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (c)."); + ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (c)."); + gorf.insert(-30, astring("eep")); + ASSERT_EQUAL(gorf, astring("tttbru"), "correct value was not returned (d)."); + ASSERT_EQUAL(gorf.length(), 6, "length is incorrect (d)."); +} + +void test_string::run_test_17() +{ +// FUNCDEF("run_test_17"); + // 17th test checks construction of temporaries. +/* this test set causes the obnoxious 16 bit codeguard error from hell, as + does use of temporary objects in ostream << operators. argh! */ + astring("jubjo"); + astring("obo"); + astring("flrrpominort").substring(3, 6); +} + +void test_string::run_test_18() +{ +#ifdef DEBUG_STRING_TEST + FUNCDEF("run_test_18"); +#endif + // 18th test group checks windows related functions. +#ifdef AFXDLL + AfxSetResourceHandle(GET_INSTANCE_HANDLE()); + // required for mfc to see the proper instance handle. + + // this tests the "constructor from resource". + astring libname = rc_string(IDS_BASIS_NAME); + ASSERT_EQUAL(libname, astring("Basis Library"), + astring("library name is a mismatch: comes out as \"") + libname + "\"."); + #define IDS_SOME_BAD_UNKNOWN_STRING_HANDLE 30802 + astring bogus_name = rc_string(IDS_SOME_BAD_UNKNOWN_STRING_HANDLE); + ASSERT_FALSE(bogus_name.length(), "bogus rc string had length"); + + // tests conversions from CString to astring and back. + astring george("yo yo yo"); + CString hal = convert(george); + astring borgia = convert(hal); + +#ifdef DEBUG_STRING_TEST + LOG(astring("cstringy conversions: ") + george); + LOG((const char *)hal); + LOG(borgia); +#endif + + ASSERT_EQUAL(borgia, george, "got the wrong value"); +#endif +} + +void test_string::run_test_19() +{ + FUNCDEF("run_test_19"); + // 19th test group is devoted to anthony wenzel's % printing bug. + astring problematic_example(astring::SPRINTF, "this should have %d% more " + "stuffing than before!", 20); +//MessageBox(0, astring("got a string of \"%s!\"", problematic_example.s()).s(), "yo!", MB_OK); + ASSERT_EQUAL(problematic_example, astring("this should have 20% more stuffing than before!"), "failure to print correct phrase"); +} + +void test_string::run_test_20() +{ +#ifdef DEBUG_STRING_TEST + FUNCDEF("run_test_20"); +#endif + // 20th test group is devoted to another wenzelbug. + + // Hey, I just found out (in an ugly way) that the following doesn't work: + char myText[] = "OK"; + astring myString(astring::SPRINTF, "%04s", myText); +#ifdef DEBUG_STRING_TEST + LOG(astring("first try gets: ") + myString); +#endif + + char myText8[] = "OK"; + char my_text_4[90]; + sprintf(my_text_4, "%4s", myText8); +#ifdef DEBUG_STRING_TEST + LOG(astring("second try gets: ") + astring(my_text_4)); +#endif + + // Looks like you don't handle the "%04s" arguments properly. I can make + // it work as follows: + char myText2[] = "OK"; + char myText3[50]; + sprintf(myText3, "%4s", myText2); + astring myString2(myText3); +#ifdef DEBUG_STRING_TEST + LOG(astring("third try gets: ") + myString2); +#endif +} + +void test_string::run_test_21() +{ + FUNCDEF("run_test_21"); + // 21st test group checks out the strip spaces and replace functions. + astring spacey(" dufunk "); + astring temp = spacey; + temp.strip_spaces(astring::FROM_FRONT); + ASSERT_EQUAL(temp, astring("dufunk "), "created wrong string"); + temp = spacey; + temp.strip_spaces(astring::FROM_END); + ASSERT_EQUAL(temp, astring(" dufunk"), "created wrong string"); + temp = spacey; + temp.strip_spaces(astring::FROM_BOTH_SIDES); + ASSERT_EQUAL(temp, astring("dufunk"), "created wrong string"); + + astring placemat("mary had a red hooded cobra she kept around her neck " + "and it hissed at the people as they walked by her tent."); + ASSERT_TRUE(placemat.replace("had", "bought"), "replace failed"); + ASSERT_TRUE(!placemat.replace("hoded", "bought"), "replace didn't fail but should have"); + ASSERT_TRUE(placemat.replace("she", "funkateria"), "replace failed"); + ASSERT_TRUE(placemat.replace("hooded", "hood"), "replace failed"); + ASSERT_TRUE(placemat.replace("cobra", "in the"), "replace failed"); + + int indy = placemat.find("kept"); + ASSERT_FALSE(negative(indy), "couldn't find string"); + placemat[indy - 1] = '.'; + placemat.zap(indy, placemat.end()); + ASSERT_EQUAL(placemat, astring("mary bought a red hood in the funkateria."), "got wrong result string"); +} + +void test_string::run_test_22() +{ + FUNCDEF("run_test_22"); + // 22nd test: morgan's find bug. + astring B("buzz*buzz*"); + { + int x = B.find('*'); // correctly finds the first *. + ASSERT_EQUAL(x, 4, "got wrong index for first"); + x++; + x = B.find('*', x); // correctly finds the second *. + ASSERT_EQUAL(x, 9, "got wrong index for second"); + x++; // now x == B.length(). + x = B.find('*', x); + // error was: finds the second * again (and again and again and + // again...). At this point it should return OUT_OF_RANGE. + ASSERT_FALSE(x > 0, "got wrong outcome for third"); + } + { + int x = B.find("z*"); // correctly finds the first z*. + ASSERT_EQUAL(x, 3, "got wrong index for fourth"); + x++; + x = B.find("z*", x); // correctly finds the second *. + ASSERT_EQUAL(x, 8, "got wrong index for fifth"); + x++; // now x == B.length(). + x = B.find("z*", x); + // error was: finds the second * again (and again and again and + // again...). At this point it should return OUT_OF_RANGE. + ASSERT_FALSE(x > 0, "got wrong outcome for sixth"); + } +} + +void test_string::run_test_23() +{ + FUNCDEF("run_test_23"); + // 23rd test: test the new strip function. + astring strip_string("!@#$%^&*()"); + + astring no_stripper("this shouldn't change"); + no_stripper.strip(strip_string, astring::FROM_BOTH_SIDES); + ASSERT_EQUAL(no_stripper, astring("this shouldn't change"), "first test failed comparison"); + + astring strippee_orig("&$(*(@&@*()()!()@*(@(*fudge#((@(*@**#)(@#)(#"); + astring strippee(strippee_orig); + strippee.strip(strip_string, astring::FROM_BOTH_SIDES); + ASSERT_EQUAL(strippee, astring("fudge"), "second test failed comparison"); + + strippee = strippee_orig; + strippee.strip(strip_string, astring::FROM_FRONT); + ASSERT_EQUAL(strippee, astring("fudge#((@(*@**#)(@#)(#"), "third test failed comparison"); + + strippee = strippee_orig; + strippee.strip(strip_string, astring::FROM_END); + ASSERT_EQUAL(strippee, astring("&$(*(@&@*()()!()@*(@(*fudge"), "fourth test failed comparison"); +} + +void test_string::run_test_24() +{ + FUNCDEF("run_test_24"); +#ifdef __WIN32__ + // 24th test group tests _bstr_t conversions. + _bstr_t beast("abcdefgh"); +#ifdef DEBUG_STRING_TEST + LOG(astring("the beast is ") + beast.operator char *() + + astring(astring::SPRINTF, " with length %d", beast.length())); +#endif + astring convert = beast; +#ifdef DEBUG_STRING_TEST + LOG(astring("the convert is ") + convert + + astring(astring::SPRINTF, " with length %d", convert.length())); +#endif + ASSERT_EQUAL(convert, astring("abcdefgh"), "first test failed comparison"); + + astring jethro("i want a hog sandwich"); + _bstr_t pork = string_convert::to_bstr_t(jethro); + ASSERT_FALSE(strcmp(pork.operator char *(), jethro.s()), "second test failed comparison"); +#endif +} + +void test_string::run_test_25() +{ + FUNCDEF("run_test_25"); + // 25th test group tests simple comparisons. + astring fred = "asdoiuaoisud"; + ASSERT_INEQUAL(fred, astring(), "first test failed comparison"); + astring bub = fred; + ASSERT_EQUAL(fred, bub, "second test failed comparison"); + fred = ""; + ASSERT_EQUAL(fred, astring(), "third test failed comparison"); + ASSERT_FALSE(fred.length(), "fourth test failed comparison"); +} + +void test_string::run_test_26() +{ +// FUNCDEF("run_test_26"); + // 26th test group does simple time_stamp::notarize operations. these are more for + // ensuring boundschecker gets to see some of this. + astring t2 = time_stamp::notarize(false); + astring t4 = time_stamp::notarize(true); +} + +void test_string::run_test_27() +{ +// FUNCDEF("run_test_27"); + // 27th test group plays around with idate in an attempt to get + // boundschecker to complain. + timely::day_in_year d1 = date_now(); + timely::clock_time t1 = time_now(); + timely::time_locus dt1 = now(); + astring sd1 = d1.text_form(); + astring st1 = t1.text_form(); + astring sdt1 = dt1.text_form_long(); +} + +void test_string::run_test_28() +{ + FUNCDEF("run_test_28"); + // 28th test group does sprintfs on shorts and such. + basis::un_int in1 = 27; + basis::un_short in2 = 39; + char in3 = 'Q'; + astring testy(astring::SPRINTF, "%u:%hu:%c", in1, in2, in3); + ASSERT_EQUAL(testy, astring("27:39:Q"), "fourth test failed comparison"); +} + +void test_string::run_test_29() +{ + FUNCDEF("run_test_29"); + // 29th test group tries out the packing support. + astring a("would an onion smell so sweet?"); + byte_array p1; + a.pack(p1); + astring b; + ASSERT_TRUE(b.unpack(p1), "first unpack failed"); + ASSERT_EQUAL(b, a, "first comparison failed"); + a = "128 salamanders cannot be wrong."; + a.pack(p1); + ASSERT_TRUE(b.unpack(p1), "second unpack failed"); + ASSERT_EQUAL(b, a, "second comparison failed"); +} + +void standard_sprintf_test(const char *parm_string) +{ +// FUNCDEF("standard_sprintf_test"); + astring print_into(' ', 20000); + print_into[0] = '\0'; +//check these!!!: + int i1 = int(rando.inclusive(0, 23945)); + long l1 = long(rando.inclusive(-2394, 2998238)); + un_int u1 = basis::un_int(rando.inclusive(0, 124395)); + un_short s1 = basis::un_short(rando.inclusive(0, 65535)); + sprintf(print_into.s(), "%d %ld %u %hu %s", i1, l1, u1, s1, parm_string); + sprintf(print_into.s(), "%c %d %c %s %s %lu", char(rando.inclusive('a', 'z')), + int(rando.inclusive(0, 23945)), char(rando.inclusive('A', 'Z')), + parm_string, parm_string, basis::un_long(rando.inclusive(0, 2998238))); +} + +void test_string::run_test_30() +{ + // 30th test group checks astring sprintf. + FUNCDEF("run_test_30"); + astring parm_string = string_manipulation::make_random_name(40, 140); + astring print_into(' ', 20000); + print_into[0] = '\0'; + int i1 = int(rando.inclusive(0, 23945)); + long l1 = long(rando.inclusive(-2394, 2998238)); + un_int u1 = basis::un_int(rando.inclusive(0, 124395)); + un_short s1 = basis::un_short(rando.inclusive(0, 65535)); + char test_same[20010]; + sprintf(test_same, "%d %ld %u %hu %s", i1, l1, u1, s1, parm_string.s()); + print_into.sprintf("%d %ld %u %hu %s", i1, l1, u1, s1, parm_string.s()); + ASSERT_EQUAL(astring(test_same), print_into, "sprintf should get same results as standard"); +//do test for this one too. + print_into.sprintf("%c %d %c %s %s %lu", char(rando.inclusive('a', 'z')), + int(rando.inclusive(0, 23945)), char(rando.inclusive('A', 'Z')), + parm_string.observe(), parm_string.s(), basis::un_long(rando.inclusive(0, 2998238))); +} + +void test_string::run_test_31() +{ + FUNCDEF("run_test_31"); + // testing of character repeat constructor. + astring dashes('-', 20); + astring non_plusses('+', 0); + astring plusses('+', 1); + ASSERT_EQUAL(dashes.length(), 20, astring("dashes has wrong length!")); + ASSERT_EQUAL(dashes, astring("--------------------"), astring("dashes has wrong contents! '") + dashes + "' vs. '" + astring("--------------------'")); + ASSERT_FALSE(non_plusses.length(), astring("non_plusses has wrong length!")); + ASSERT_EQUAL(plusses.length(), 1, astring("plusses has wrong length!")); + ASSERT_EQUAL(plusses, astring("+"), astring("plusses has wrong contents!")); + ASSERT_EQUAL(plusses, astring('+', 1), astring("plusses second compare failed!")); +} + +void test_string::run_test_32() +{ + FUNCDEF("run_test_32"); + // tests creating a huge string and ripping it apart. + + const int CHUNK_SIZE = 2 * MEGABYTE; + // the block factor for our string. we expect not to go above this during + // the testing. + const int MIN_ADDITION = 10000; const int MAX_ADDITION = 80000; + // these are the largest chunk we add to a string at a time. + const int BUILD_AND_BURN_ITERATIONS = 1; + // number of times to test building up and tearing down. + + // the string we build up and tear down. + astring slab; + +//hmmm: maybe have a mixed phase where tearing and adding +// happens frequently and interspersed. + + for (int iters = 0; iters < BUILD_AND_BURN_ITERATIONS; iters++) { + + int size = 0; // independent count of expected size. + // we don't want to bother allocating the big chunk more than once, so + // we'll add to the string until it's within range of going over. + while (slab.length() < CHUNK_SIZE - MAX_ADDITION - 20) { +//make this into add_a_chunk + astring addition = string_manipulation::make_random_name(MIN_ADDITION, + MAX_ADDITION); + slab += addition; + size += addition.length(); + ASSERT_EQUAL(size, slab.length(), astring("size is incorrect after add!")); + } + + // now we eat it up. + while (slab.length()) { +//make this into zap_a_chunk + int zap_start = rando.inclusive(0, slab.end()); + int zap_end = rando.inclusive(0, slab.end()); + flip_increasing(zap_start, zap_end); + int range_length = zap_end - zap_start + 1; + ASSERT_FALSE(negative(range_length), astring("flip_increasing failed!")); + slab.zap(zap_start, zap_end); + size -= range_length; + ASSERT_EQUAL(size, slab.length(), astring("size is incorrect after zap!")); + } + } +} + +void test_string::run_test_33() +{ + FUNCDEF("run_test_33"); + // 33rd test group exercises the replace functions. + { + astring to_modify("\\\\feeby\\path\\yo"); + ASSERT_TRUE(to_modify.replace("\\", "/"), "failed to replace the string"); + ASSERT_EQUAL(to_modify, astring("/\\feeby\\path\\yo"), "produced wrong resultant string"); + while (to_modify.replace("\\", "/")) {} + ASSERT_EQUAL(to_modify, astring("//feeby/path/yo"), "produced wrong final string"); + } + { + astring to_modify("\\superduper\\dynamo\\looper"); + ASSERT_TRUE(to_modify.replace("\\", "/"), "failed to replace the string"); + ASSERT_EQUAL(to_modify, astring("/superduper\\dynamo\\looper"), "produced wrong resultant string"); + while (to_modify.replace("\\", "/")) {} + ASSERT_EQUAL(to_modify, astring("/superduper/dynamo/looper"), "produced wrong final string"); + } + { + astring id = "/SRV=1/SRC=1"; + astring id1 = id; + while (id1.replace("/", " ")) {} +// LOG(astring("replacing / with ' ' in first test (was ") + id + +// ") gives " + id1); + ASSERT_EQUAL(id1, astring(" SRV=1 SRC=1"), "produced wrong final string"); + + astring id2 = id; + while (id2.replace("=", ":")) {} +// LOG(astring("replacing = with : in second test (was ") + id + +// ") gives " + id2); + ASSERT_EQUAL(id2, astring("/SRV:1/SRC:1"), "produced wrong final string"); + } +} + +void test_string::run_test_34() +{ +// FUNCDEF("run_test_34"); + +//not in use right now. + +} + +void test_string::run_test_35() +{ + FUNCDEF("run_test_35"); + // test the shrink method. + astring termo('R', 2812); + ASSERT_EQUAL(termo.length(), 2812, "length should be as requested"); + termo[1008] = '\0'; + termo.shrink(); + ASSERT_EQUAL(termo.get_implementation().internal_real_length(), 1010, a_sprintf("failure in shrunken size: " "wanted 1010 and got %d.", termo.get_implementation().internal_real_length())); + astring termo2('R', 1008); + ASSERT_EQUAL(termo, termo2, "wrong value produced"); +} + +void test_string::run_test_36() +{ + FUNCDEF("run_test_36"); + // test the text form on a string array (which is mildly related to strings; + // this could be moved to a common templates test someday). + string_array torpid; + torpid += "york"; + torpid += "burger"; + torpid += "petunia"; + torpid += "dumptruck"; + ASSERT_EQUAL(torpid.text_form(), astring("\"york\",\"burger\",\"petunia\",\"dumptruck\""), "wrong value computed"); + string_array sacral; + sacral += "gumboat"; + ASSERT_EQUAL(sacral.text_form(), astring("\"gumboat\""), "wrong value computed"); + + string_array paknid; + paknid += "gorboochy"; + paknid += "rangolent"; + byte_array packed; + structures::pack_array(packed, paknid); + + string_array upnort; + ASSERT_TRUE(structures::unpack_array(packed, upnort), "failed to unpack"); + ASSERT_FALSE(packed.length(), "array still has bytes!"); + + string_array stongent; + stongent += "pemulack"; + stongent += "bluzzent"; + stongent += "crupto"; + stongent += "floonack"; + stongent += "atoona"; + packed.reset(); + structures::pack_array(packed, stongent); + + string_array belzorp; + ASSERT_TRUE(structures::unpack_array(packed, belzorp), "failed to unpack"); + ASSERT_FALSE(packed.length(), "array still has bytes!"); +} + +void test_string::run_test_37() +{ + FUNCDEF("run_test_37"); + // 37th test group used to try out the old packing support, but now is + // just the same as test 29. it would be good to make this different. + astring a("would an onion smell so sweet?"); + byte_array p1; + a.pack(p1); + astring b; + ASSERT_TRUE(b.unpack(p1), "first unpack failed"); + ASSERT_EQUAL(b, a, "first comparison failed"); + a = "128 salamanders cannot be wrong."; + a.pack(p1); + ASSERT_TRUE(b.unpack(p1), "second unpack failed"); + ASSERT_EQUAL(b, a, "second comparison failed"); +} + +void test_string::run_test_38() +{ +// FUNCDEF("run_test_38"); + double to_print = 2.345; + a_sprintf non_deadly("%.1f", to_print); +/// LOG(astring("printed: ") + non_deadly); + + to_print = 1.797E+254; + // this value breaks things. + + char bucket[2000]; + bucket[0] = '\0'; + sprintf(bucket, "%.1f", to_print); +/// LOG(astring("sprintf printed: ") + bucket); + + a_sprintf deadly("%.1f", to_print); +/// LOG(astring("printed: ") + deadly); +} + +void test_string::run_test_39() +{ + FUNCDEF("run_test_39"); + const char *find_set = "!?.;"; + astring test_1 = "how do i get to balthazar square? it stinks!"; + ASSERT_EQUAL(test_1.find_any(find_set), 32, "first find returned wrong result"); + ASSERT_EQUAL(test_1.find_any(find_set, 33), 44, "second find returned wrong result"); + ASSERT_EQUAL(test_1.find_any(find_set, 40, true), 32, "third find returned wrong result"); +} + +void test_string::run_test_40() +{ + FUNCDEF("run_test_40"); + int test_num = 1; + #define test_name() a_sprintf("test %d: ", test_num) + { + astring target = "xabab"; + astring from = "ab"; + astring to = "dc"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("xdcdc"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "xabab"; + astring from = "ab"; + astring to = "ab"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("xabab"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "xabab"; + astring from = "ab"; + astring to = "a"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("xaa"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "ababx"; + astring from = "ab"; + astring to = "a"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("aax"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "suzzle rumpetzzes gnargle rezztor"; + astring from = "zz"; + astring to = "zzz"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("suzzzle rumpetzzzes gnargle rezzztor"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "qqqq"; + astring from = "q"; + astring to = "qqq"; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("qqqqqqqqqqqq"), test_name() + "didn't replace properly"); + test_num++; + } + { + astring target = "glorg snorp pendle funk"; + astring from = " "; + astring to = ""; + ASSERT_TRUE(target.replace_all(from, to), test_name() + "didn't find from string"); + ASSERT_EQUAL(target, astring("glorgsnorppendlefunk"), test_name() + "didn't replace properly"); + test_num++; + } +} + +void test_string::run_test_41() +{ + FUNCDEF("run_test_41"); + int test_num = 0; + #define test_name() a_sprintf("test %d: ", test_num) + { + test_num++; + astring target = "xabab"; + const char *finding1 = "ab"; + ASSERT_EQUAL(target.find_non_match(finding1, 0, false), 0, test_name() + "didn't find right location A"); + const char *finding2 = "xb"; + ASSERT_EQUAL(target.find_non_match(finding2, target.length() - 1, true), 3, test_name() + "didn't find right location B"); + const char *finding3 = "c"; + ASSERT_EQUAL(target.find_non_match(finding3, 0, false), 0, test_name() + "wrong answer for test C"); + ASSERT_EQUAL(target.find_non_match(finding3, target.length() - 1, true), target.length() - 1, + test_name() + "wrong answer for test D"); + } + { + test_num++; + astring target = "abcadefghoota"; + const char *finding1 = "bcdfghot"; + ASSERT_EQUAL(target.find_non_match(finding1, 0, false), 0, test_name() + "didn't find right location A"); + ASSERT_EQUAL(target.find_non_match(finding1, 1, false), 3, test_name() + "didn't find right location B"); + ASSERT_EQUAL(target.find_non_match(finding1, target.length() - 1, true), target.length() - 1, test_name() + "didn't find right location C"); + ASSERT_EQUAL(target.find_non_match(finding1, target.length() - 2, true), 5, test_name() + "didn't find right location D"); + ASSERT_EQUAL(target.find_non_match(finding1, 3, false), 3, test_name() + "didn't find right location E"); + ASSERT_EQUAL(target.find_non_match(finding1, 4, false), 5, test_name() + "didn't find right location F"); + + } + +} + +// exercise the middle, right and left methods. +void test_string::run_test_42() +{ + FUNCDEF("run_test_42"); + + astring hobnob("all the best robots are bending robots"); + + ASSERT_EQUAL(hobnob.middle(5, 7), astring("he best"), "failed to find middle of string"); + ASSERT_EQUAL(hobnob.right(10), astring("ing robots"), "failed to find right side of string"); + ASSERT_EQUAL(hobnob.left(6), astring("all th"), "failed to find right side of string"); +} + +////////////// + +int test_string::execute() +{ + FUNCDEF("execute"); + +// ASSERT_EQUAL(0, 1, "fake assertion to test jenkins log parsing"); + ASSERT_EQUAL(1, 1, "non-fake assertion to test jenkins log parsing"); + + ASSERT_EQUAL(staticity_test, astring("yo!"), "wrong contents"); + + time_stamp end_time(TEST_RUNTIME_DEFAULT); + // when we're done testing. + + int i = 0; // iteration counter. + while (time_stamp() < end_time) { + // we run the test until our time runs out. + i++; // next iteration. +#ifdef DEBUG_STRING_TEST + LOG(astring(astring::SPRINTF, "index %d", i)); +#endif + + // beginning of test sets. + run_test_01(); + run_test_02(); + run_test_03(); + run_test_04(); + run_test_05(); + run_test_06(); + run_test_07(); + run_test_08(); + run_test_09(); + run_test_10(); + run_test_11(); + run_test_12(); + run_test_13(); + run_test_14(); + run_test_15(); + run_test_16(); + run_test_17(); + run_test_18(); + run_test_19(); + run_test_20(); + run_test_21(); + run_test_22(); + run_test_23(); + run_test_24(); + run_test_25(); + run_test_26(); + run_test_27(); + run_test_28(); + run_test_29(); +//too slow run_test_30(); + run_test_31(); + run_test_32(); + run_test_33(); +//retired run_test_34(); + run_test_35(); + run_test_36(); + run_test_37(); + run_test_38(); + run_test_39(); + run_test_40(); + run_test_41(); + run_test_42(); + } + + return final_report(); +} + +HOOPLE_MAIN(test_string, ) + + diff --git a/nucleus/library/tests_basis/test_system_preconditions.cpp b/nucleus/library/tests_basis/test_system_preconditions.cpp new file mode 100644 index 00000000..f18f17d6 --- /dev/null +++ b/nucleus/library/tests_basis/test_system_preconditions.cpp @@ -0,0 +1,169 @@ +/* +* Name : test_system_preconditions +* Author : Chris Koeritz +** +* Copyright (c) 1993-$now By Author. 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 2 of +* the License or (at your option) any later version. This is online at: +* http://www.fsf.org/copyleft/gpl.html +* Please send any updates to: fred@gruntose.com +*/ + +#include "checkup.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace system_checkup; +using namespace structures; +using namespace unit_test; + +class test_system_preconditions : virtual public unit_base, virtual public application_shell +{ +public: + test_system_preconditions() : application_shell() {} + DEFINE_CLASS_NAME("test_system_preconditions"); + virtual int execute(); +}; + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT c_parent + +class burpee +{ +public: + burpee(unit_base &parent) : c_parent(parent), my_string(new astring) { *my_string = "balrog"; } + DEFINE_CLASS_NAME("burpee"); + virtual ~burpee() { + FUNCDEF("destructor"); + WHACK(my_string); + ASSERT_FALSE(my_string, "whack test should not fail to clear string"); + } + +protected: + unit_base &c_parent; + +private: + astring *my_string; +}; + +#undef UNIT_BASE_THIS_OBJECT + +////////////// + +#define UNIT_BASE_THIS_OBJECT c_parent + +class florba : public burpee +{ +public: + florba(unit_base &parent) : burpee(parent), second_string(new astring) + { *second_string = "loquacious"; } + DEFINE_CLASS_NAME("florba"); + virtual ~florba() { + FUNCDEF("destructor"); + WHACK(second_string); + ASSERT_FALSE(second_string, "whack test should clear string in derived class"); + } + +private: + astring *second_string; +}; + +#undef UNIT_BASE_THIS_OBJECT + +////////////// + +// back to default now. +#define UNIT_BASE_THIS_OBJECT (*this) + +struct testing_file_struct : public FILE {}; + +// NOTE: an important part of this test program is running it under something +// like boundschecker to ensure that there are no memory leaks caused by +// invoking WHACK. apparently diab 3 is unable to implement WHACK correctly. + +int test_system_preconditions::execute() +{ + FUNCDEF("execute") + // let's see what this system is called. + log(astring("The name of this software system is: ") + + application_configuration::software_product_name()); + ASSERT_TRUE(strlen(application_configuration::software_product_name()), + "product should not be blank"); + + // and what this program is called. + log(astring("The application is called: ") + application_configuration::application_name()); + ASSERT_TRUE(application_configuration::application_name().length(), + "application name should not be blank"); + + // testing compiler's ansi c++ compliance. + for (int q = 0; q < 198; q++) { + int treno = q; + int malfoy = treno * 3; +// log(a_sprintf("%d", malfoy)); + } + // this should not be an error. the scope of q should be within the loop and + // not outside of it. + int q = 24; + ASSERT_FALSE(q > 190, "no weirdness should happen with compiler scoping"); + + // test that the WHACK function operates properly. + burpee *chunko = new burpee(*this); + florba *lorkas = new florba(*this); + burpee *alias = lorkas; + + WHACK(chunko); + WHACK(alias); + ASSERT_FALSE(chunko, "chunko whack test should succeed"); + ASSERT_FALSE(alias, "aliased lorkas whack test should succeed"); + ASSERT_TRUE(lorkas, "original lorkas should not have been cleared"); + lorkas = NIL; + + ASSERT_EQUAL((int)sizeof(testing_file_struct), (int)sizeof(FILE), + "struct size test, sizeof testing_file_struct and sizeof FILE should not differ"); + + // now do the crucial tests on the OS, platform, compiler, etc. + ASSERT_TRUE(check_system_characteristics(*this), + "required system characteristics should be found"); + +#ifdef __WIN32__ + known_operating_systems os = determine_OS(); + if (os == WIN_95) + printf("This is windows 95.\n"); + else if (os == WIN_NT) + printf("This is windows NT.\n"); + else if (os == WIN_2K) + printf("This is windows 2000.\n"); + else if (os == WIN_XP) + printf("This is windows XP.\n"); + else + printf("This OS is unknown.\n"); +#endif + + version os_ver = application_configuration::get_OS_version(); + printf("OS version: %s\n", os_ver.text_form().s()); + + return final_report(); +} + +HOOPLE_MAIN(test_system_preconditions, ) + diff --git a/nucleus/library/tests_configuration/makefile b/nucleus/library/tests_configuration/makefile new file mode 100644 index 00000000..04995769 --- /dev/null +++ b/nucleus/library/tests_configuration/makefile @@ -0,0 +1,10 @@ +include cpp/variables.def + +PROJECT = tests_configuration +TYPE = test +TARGETS = test_section_manager.exe test_tokenizer.exe +LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ + structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def diff --git a/nucleus/library/tests_configuration/test_section_manager.cpp b/nucleus/library/tests_configuration/test_section_manager.cpp new file mode 100644 index 00000000..c8358237 --- /dev/null +++ b/nucleus/library/tests_configuration/test_section_manager.cpp @@ -0,0 +1,124 @@ +/* +* Name : test_section_manager +* Author : Chris Koeritz +* Purpose: Tests that the section manager is writing sections properly and keeping its + table of contents correctly. +** +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +//#define DEBUG_SECTION_MANAGER + // uncomment for debugging version. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +//#ifdef DEBUG_SECTION_MANAGER +// #include +//#endif + +class test_section_manager : public virtual unit_base, virtual public application_shell +{ +public: + test_section_manager() {} + DEFINE_CLASS_NAME("test_section_manager"); + virtual int execute(); +}; + +////////////// + +int test_section_manager::execute() +{ + FUNCDEF("execute"); + { + astring TEST = "First Test"; + ini_configurator ini("t_section_manager_1.ini", ini_configurator::AUTO_STORE); + section_manager mangler(ini, "TOC", "BORK:"); + // clean up the ini file for our testing.... + string_array names; + if (mangler.get_section_names(names)) { + for (int i = 0; i < names.length(); i++) mangler.zap_section(names[i]); + ini.delete_section("TOC"); // remove table of contents. + } + + // now add some entries... + string_table contents1; + contents1.add("oink", "bozoot"); + contents1.add("gink", "rinkum"); + contents1.add("sorty", "figulat"); + contents1.add("crinkish", "wazir"); + ASSERT_TRUE(mangler.add_section("burny", contents1), + TEST + ": couldn't add the first section!"); + string_table temp_1; + ASSERT_TRUE(mangler.find_section("burny", temp_1), + TEST + ": couldn't retrieve the first section!"); +#ifdef DEBUG_SECTION_MANAGER + printf("first section has:\n%s\n", temp_1.text_form().s()); + printf("we want:\n%s\n", contents1.text_form().s()); +#endif + ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect!"); + contents1.add("glurp", "locutusburger"); + ASSERT_FALSE(mangler.add_section("burny", contents1), + TEST + ": incorrectly allowing re-add of first section!"); + ASSERT_TRUE(mangler.replace_section("burny", contents1), + TEST + ": failing to replace first section!"); + temp_1.reset(); + ASSERT_TRUE(mangler.find_section("burny", temp_1), + TEST + ": couldn't retrieve the first section (2)!"); + ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect (2)!"); + + string_table contents2; + contents2.add("tsingha", "tsinglo"); + contents2.add("chunk", "midgets"); + contents2.add("burn", "barns in texas"); + contents2.add("chump", "will not be elected"); + contents2.add("geezerplant", "water weekly"); + ASSERT_TRUE(mangler.add_section("itchy", contents2), + TEST + ": couldn't add the second section!"); + string_table temp_2; + ASSERT_TRUE(mangler.find_section("itchy", temp_2), + TEST + ": couldn't retrieve the second section!"); + ASSERT_EQUAL(temp_2, contents2, TEST + ": second section's contents are incorrect!"); + // test that first section is still there with second having been added. + ASSERT_TRUE(mangler.find_section("burny", temp_1), + TEST + ": couldn't retrieve the first section (3)!"); + ASSERT_EQUAL(temp_1, contents1, TEST + ": first section's contents are incorrect (3)!"); + +//more! + } + { +// astring TEST = "Second Test"; + } + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_section_manager, ); + diff --git a/nucleus/library/tests_configuration/test_tokenizer.cpp b/nucleus/library/tests_configuration/test_tokenizer.cpp new file mode 100644 index 00000000..62b5ea8f --- /dev/null +++ b/nucleus/library/tests_configuration/test_tokenizer.cpp @@ -0,0 +1,285 @@ +/* +* Name : test_tokenizer +* Author : Chris Koeritz +* Purpose: Puts the variable_tokenizer through some paces. +** +* Copyright (c) 1998-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +const int MAX_LINE_SIZE = 1000; + // the largest line we will deal with in a file. + +class test_tokenizer : public virtual unit_base, public virtual application_shell +{ +public: + test_tokenizer() {} + DEFINE_CLASS_NAME("test_tokenizer"); + virtual int execute(); +}; + +////////////// + +int test_tokenizer::execute() +{ + FUNCDEF("execute"); + { + astring test_set_1 = "\n\ +[frederick]\n\ +samba=dance\n\ +tantalus rex=gumby\n\ +57 chevy heap=\"16 anagrams of misty immediately\"\n\ +lingus distractus\n\ +shouldus havus assignmentum=\n\ +above better be parsed = 1\n\ +;and this comment too yo\n\ +ted=agent 12\n"; + + astring TEST = "First Test: "; + astring testing = test_set_1; + LOG(astring("file before parsing:") + testing); + variable_tokenizer jed("\n\r", "="); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + variable_tokenizer gorp("\n\r", "="); + ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); + LOG(astring("file after parsing:") + out); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + +//for (int i = 0; i < gorp.table().symbols(); i++) { +//astring name, value; +//gorp.table().retrieve(i, name, value); +//LOG(a_sprintf("item %d: name=\"%s\" value=\"%s\"", i, name.s(), value.s())); +//} + + ASSERT_TRUE(jed.exists("[frederick]"), TEST + "jed section header was omitted!"); + ASSERT_EQUAL(jed.find("[frederick]"), astring(""), + TEST + "jed section header had unexpected contents!"); + ASSERT_EQUAL(jed.find("ted"), astring("agent 12"), + TEST + "jed's ted is missing or invalid!"); + ASSERT_FALSE(jed.find("shouldus havus assignmentum").t(), + TEST + "jed's shouldus had contents but shouldn't!"); + astring value = *jed.table().find("shouldus havus assignmentum"); + ASSERT_EQUAL(value, astring(" "), TEST + "jed shouldus had wrong contents, not special!"); + ASSERT_TRUE(gorp.exists("[frederick]"), TEST + "gorp section header was omitted!"); + ASSERT_EQUAL(gorp.find("[frederick]"), astring(""), + TEST + "gorp section header had unexpected contents!"); + ASSERT_EQUAL(gorp.find("ted"), astring("agent 12"), + TEST + "gorp's ted is missing or invalid!"); + ASSERT_FALSE(gorp.find("shouldus havus assignmentum").t(), + TEST + "gorp's shouldus had contents but shouldn't!"); + value = *gorp.table().find("shouldus havus assignmentum"); + ASSERT_EQUAL(value, astring(" "), TEST + "gorp shouldus had wrong contents, not special!"); + } + { + astring test_set_2 = "Name=SRV, Parent=, Persist=Y, Entry=Y, Required=Y, Desc=Server, Tbl=Server"; + + astring TEST = "Second Test: "; + astring testing = test_set_2; + LOG(astring("file before parsing:") + testing); + variable_tokenizer jed(",", "="); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + LOG(astring("file after parsing:") + out); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + ASSERT_EQUAL(jed.find("Name"), astring("SRV"), TEST + "Name is missing or invalid!"); + ASSERT_FALSE(jed.find("Parent").t(), TEST + "Parent had contents but shouldn't!"); + astring value = *jed.table().find("Parent"); + ASSERT_EQUAL(value, astring(" "), TEST + "Parent had wrong contents, not special!"); + ASSERT_EQUAL(jed.find("Persist"), astring("Y"), TEST + "Persist is missing or invalid!"); + } + + { + astring test_set_3 = "\n\ +[frederick]\n\ +samba=dance\n\ +tantalus rex=gumby \"don#t\n\n'play'\nthat\" homey '\n\ndog\n\n yo \"\ncreen\" arf'\n\ +57 chevy heap=\"16 anagrams of misty immediately\"\n\ +lingus distractus\n\ +shouldus havus assignmentum=\n\ +above better be parsed = 1\n\ +;and this comment too yo\n\ +ted=agent 12\n"; + + astring TEST = "Third Test: "; + astring testing = test_set_3; + LOG(astring("file before parsing:") + testing); + variable_tokenizer jed("\n\r", "=", "\'\""); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + variable_tokenizer gorp("\n\r", "=", "\'\""); + ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); + LOG(astring("file after parsing:") + out); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + ASSERT_TRUE(jed.exists("[frederick]"), TEST + "jed section header was omitted!"); + ASSERT_EQUAL(jed.find("[frederick]"), astring(""), + TEST + "jed section header had unexpected contents!"); + ASSERT_EQUAL(jed.find("ted"), astring("agent 12"), TEST + "jed ted is missing or invalid!"); + ASSERT_FALSE(jed.find("shouldus havus assignmentum").t(), + TEST + "jed shouldus had contents but shouldn't!"); + astring value = *jed.table().find("shouldus havus assignmentum"); + ASSERT_EQUAL(value, astring(" "), TEST + "jed shouldus had wrong contents, not special!"); + ASSERT_TRUE(gorp.exists("[frederick]"), TEST + "gorp section header was omitted!"); + ASSERT_EQUAL(gorp.find("[frederick]"), astring(""), + TEST + "gorp section header had unexpected contents!"); + ASSERT_EQUAL(gorp.find("ted"), astring("agent 12"), TEST + "gorp second ted is missing or invalid!"); + ASSERT_FALSE(gorp.find("shouldus havus assignmentum").t(), + TEST + "gorp shouldus had contents but shouldn't!"); + value = *gorp.table().find("shouldus havus assignmentum"); + ASSERT_EQUAL(value, astring(" "), TEST + "gorp shouldus wrong contents, was not special!"); + ASSERT_TRUE(gorp.exists("tantalus rex"), TEST + "gorp tantalus rex is missing!"); + ASSERT_EQUAL(gorp.find("tantalus rex"), + astring("gumby \"don#t\n\n'play'\nthat\" homey '\n\ndog\n\n yo " + "\"\ncreen\" arf'"), + TEST + "gorp tantalus rex has incorrect contents!"); + } + { + astring test_set_4 = "\n\ +[garfola]\n\ +treadmill=\"this ain't the place\nwhere'n we been done\nseein' no quotes\"\n\ +borfulate='similarly \"we\" do not like\nthe \" quote \" type thing here'\n\ +"; + + astring TEST = "Fourth Test: "; + astring testing = test_set_4; + LOG(astring("file before parsing:\n") + testing); + variable_tokenizer jed("\n\r", "=", "\'\"", false); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + variable_tokenizer gorp("\n\r", "=", "\'\"", false); + ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); + LOG(astring("file after parsing:\n") + out); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + ASSERT_TRUE(gorp.exists("[garfola]"), TEST + "section header was omitted!"); + ASSERT_EQUAL(gorp.find("[garfola]"), astring(""), + TEST + "section header had unexpected contents!"); + ASSERT_TRUE(gorp.exists("treadmill"), TEST + "treadmill is missing!"); + ASSERT_EQUAL(gorp.find("treadmill"), + astring("\"this ain't the place\nwhere'n we been done\nseein' no quotes\""), + TEST + "treadmill has incorrect contents!"); + ASSERT_TRUE(gorp.exists("borfulate"), TEST + "borfulate is missing!"); + ASSERT_EQUAL(gorp.find("borfulate"), + astring("'similarly \"we\" do not like\nthe \" quote \" type thing here'"), + TEST + "borfulate has incorrect contents!"); + } + { + astring test_set_5 = "\n\ + x~35; y~92 ;#comment ; d ~83 ; e~ 54 ; ? new comment ;sud ~ xj23-8 ; nigh ~2"; + + astring TEST = "Fifth Test: "; + astring testing = test_set_5; + LOG(astring("file before parsing:\n") + testing); + variable_tokenizer jed(";", "~"); + jed.set_comment_chars("#?"); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + LOG(astring("file after parsing:\n") + out); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + + variable_tokenizer gorp(";", "~"); + gorp.set_comment_chars("#?"); + ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); + LOG("gorp in tabular form:"); + LOG(gorp.table().text_form()); +//hmmm: need equalizable/orderable on table? + ASSERT_TRUE(gorp.table() == jed.table(), TEST + "gorp text not same as jed!"); + + ASSERT_EQUAL(jed.find("x"), astring("35"), TEST + "value for x missing or invalid"); + ASSERT_EQUAL(jed.find("y"), astring("92"), TEST + "value for y missing or invalid"); + ASSERT_EQUAL(jed.find("d"), astring("83"), TEST + "value for d missing or invalid"); + ASSERT_EQUAL(jed.find("e"), astring("54"), TEST + "value for e missing or invalid"); + ASSERT_EQUAL(jed.find("sud"), astring("xj23-8"), TEST + "value for sud missing or invalid"); + ASSERT_EQUAL(jed.find("nigh"), astring("2"), TEST + "value for nigh missing or invalid"); + } + { + astring test_set_6 = "\r\n\r\n\r\ +# this is yet another test with comments.\r\n\ +; we want to be sure stuff works right.\r\n\ +crumpet=tempest\r\n\ + moomar=18\r\n\ +shagbot =once upon a time there was a man \r\n\ +\t\t\tpunzola megamum =brandle the handle \r\n\ +trapzoot= uhhh\r\n\ +mensch = racer X\r\n\ +\r\n\r\n\r\n"; + + astring TEST = "Sixth Test: "; + astring testing = test_set_6; + LOG(astring("file before parsing:\n===========\n") + testing + "\n==========="); + variable_tokenizer jed("\n\r", "="); + jed.set_comment_chars("#;"); + ASSERT_TRUE(jed.parse(testing), TEST + "jed should be parseable"); + astring out; + jed.text_form(out); + LOG(astring("file after parsing:\n===========\n") + out + "\n==========="); + LOG("and in tabular form:"); + LOG(jed.table().text_form()); + + variable_tokenizer gorp("\n\r", "="); + gorp.set_comment_chars("#;"); + ASSERT_TRUE(gorp.parse(out), TEST + "gorp should be parseable"); + LOG("gorp in tabular form:"); + LOG(gorp.table().text_form()); +LOG(a_sprintf("gorp has %d fields, jed has %d fields", gorp.symbols(), jed.symbols())); + ASSERT_TRUE(gorp.table() == jed.table(), TEST + "gorp text not same as jed!"); + + ASSERT_EQUAL(jed.find("crumpet"), astring("tempest"), + TEST + "value for crumpet missing or invalid"); + ASSERT_EQUAL(jed.find("moomar"), astring("18"), + TEST + "value for moomar missing or invalid"); + ASSERT_EQUAL(jed.find("shagbot"), astring("once upon a time there was a man"), + TEST + "value for shagbot missing or invalid"); + ASSERT_EQUAL(jed.find("trapzoot"), astring("uhhh"), + TEST + "value for trapzoot missing or invalid"); + ASSERT_EQUAL(jed.find("punzola megamum"), astring("brandle the handle"), + TEST + "value for punzola missing or invalid"); + ASSERT_EQUAL(jed.find("mensch"), astring("racer X"), + TEST + "value for mensch missing or invalid"); + } + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_tokenizer, ); + diff --git a/nucleus/library/tests_crypto/makefile b/nucleus/library/tests_crypto/makefile new file mode 100644 index 00000000..d7d3f49e --- /dev/null +++ b/nucleus/library/tests_crypto/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = tests_crypto +TYPE = test +TARGETS = test_blowfish_crypto.exe test_rsa_crypto.exe +LOCAL_LIBS_USED = unit_test crypto application processes loggers configuration textual timely \ + filesystem structures basis +USE_SSL = t +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_crypto/test_blowfish_crypto.cpp b/nucleus/library/tests_crypto/test_blowfish_crypto.cpp new file mode 100644 index 00000000..e6fdd4b0 --- /dev/null +++ b/nucleus/library/tests_crypto/test_blowfish_crypto.cpp @@ -0,0 +1,194 @@ +/* +* Name : test blowfish encryption +* Author : Chris Koeritz +* Purpose: Exercises the BlowFish encryption methods in the crypto library. +** +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace crypto; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) + +//#define DEBUG_BLOWFISH + // uncomment for noisier run. + +const int TEST_RUNS_PER_KEY = 5; // encryption test cycles done on each key. + +const int THREAD_COUNT = 10; // number of threads testing blowfish at once. + +const int ITERATIONS = 4; // number of test runs in our testing threads. + +const int MAX_STRING = 20000; // largest chunk that we'll try to encrypt. + +////////////// + +class test_blowfish; // forward. + +class blowfish_thread : public ethread +{ +public: + blowfish_thread(test_blowfish &parent) : ethread(), _parent(parent) {} + + void perform_activity(void *ptr); + // try out random blowfish keys on randomly chosen chunks of the fodder. + +private: + test_blowfish &_parent; +}; + +////////////// + +class test_blowfish : virtual public unit_base, virtual public application_shell +{ +public: + test_blowfish() + : _fodder(string_manipulation::make_random_name(MAX_STRING + 1, MAX_STRING + 1)) {} + DEFINE_CLASS_NAME("test_blowfish"); + + int execute(); + +private: + astring _fodder; // chunks taken from this are encrypted and decrypted. + time_stamp _program_start; // the time at which we started executing. + thread_cabinet _threads; // manages our testing threads. + friend class blowfish_thread; // bad practice, but saves time in test app. +}; + +int test_blowfish::execute() +{ + FUNCDEF("execute"); + int left = THREAD_COUNT; + while (left--) { + _threads.add_thread(new blowfish_thread(*this), true, NIL); + } + + while (_threads.threads()) { +#ifdef DEBUG_BLOWFISH + LOG(astring("cleaning debris.")); +#endif + _threads.clean_debris(); + time_control::sleep_ms(1000); + } + +#ifdef DEBUG_BLOWFISH + int duration = int(time_stamp().value() - _program_start.value()); + LOG(a_sprintf("duration for %d keys and encrypt/decrypt=%d ms,", + ITERATIONS * TEST_RUNS_PER_KEY * THREAD_COUNT, duration)); + LOG(a_sprintf("that comes to %d ms per cycle.\n", int(double(duration + / TEST_RUNS_PER_KEY / ITERATIONS / THREAD_COUNT)))); +#endif + + return final_report(); +} + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) + +void blowfish_thread::perform_activity(void *) +{ + FUNCDEF("perform_activity"); + int left = ITERATIONS; + while (left--) { + time_stamp key_start; + blowfish_crypto bc(_parent.randomizer().inclusive + (blowfish_crypto::minimum_key_size(), + blowfish_crypto::maximum_key_size())); +#ifdef DEBUG_BLOWFISH + LOG(a_sprintf("%d bit key has:", bc.key_size())); + astring dumped_key = byte_formatter::text_dump(bc.get_key()); + LOG(a_sprintf("%s", dumped_key.s())); +#endif + int key_dur = int(time_stamp().value() - key_start.value()); + +#ifdef DEBUG_BLOWFISH + LOG(a_sprintf(" key generation took %d ms", key_dur)); +#endif + + for (int i = 0; i < TEST_RUNS_PER_KEY; i++) { + byte_array key; + byte_array iv; + + int string_start = _parent.randomizer().inclusive(0, MAX_STRING - 1); + int string_end = _parent.randomizer().inclusive(0, MAX_STRING - 1); + flip_increasing(string_start, string_end); + astring ranstring = _parent._fodder.substring(string_start, string_end); +//LOG(a_sprintf("encoding %s\n", ranstring.s()); +//LOG(a_sprintf("string length encoded: %d\n", ranstring.length()); + + byte_array target; + + time_stamp test_start; + bool worked = bc.encrypt(byte_array(ranstring.length() + 1, + (abyte*)ranstring.s()), target); + int enc_durat = int(time_stamp().value() - test_start.value()); + ASSERT_TRUE(worked, "phase 1 should not fail to encrypt the string"); + + byte_array recovered; + test_start.reset(); + worked = bc.decrypt(target, recovered); + int dec_durat = int(time_stamp().value() - test_start.value()); + ASSERT_TRUE(worked, "phase 1 should not fail to decrypt the string"); +// LOG(a_sprintf("original has %d chars, recovered has %d chars\n", +// ranstring.length(), recovered.length() - 1)); + + astring teddro = (char *)recovered.observe(); +//LOG(a_sprintf("decoded %s\n", teddro.s())); + +#ifdef DEBUG_BLOWFISH + if (teddro != ranstring) { + LOG(a_sprintf("error!\toriginal has %d chars, recovered has %d chars\n", + ranstring.length(), recovered.length() - 1)); + LOG(a_sprintf("\tencoded %s\n", ranstring.s())); + LOG(a_sprintf("\tdecoded %s\n", teddro.s())); + } +#endif + ASSERT_EQUAL(teddro, ranstring, "should not fail to regenerate the original string"); + +#ifdef DEBUG_BLOWFISH + LOG(a_sprintf(" encrypt %d ms, decrypt %d ms, data %d bytes\n", + enc_durat, dec_durat, string_end - string_start + 1)); +#endif + time_control::sleep_ms(0); // take a rest. + } + time_control::sleep_ms(0); // take a rest. + } +} + +HOOPLE_MAIN(test_blowfish, ) + diff --git a/nucleus/library/tests_crypto/test_rsa_crypto.cpp b/nucleus/library/tests_crypto/test_rsa_crypto.cpp new file mode 100644 index 00000000..56529125 --- /dev/null +++ b/nucleus/library/tests_crypto/test_rsa_crypto.cpp @@ -0,0 +1,205 @@ +/* +* Name : test RSA public key encryption +* Author : Chris Koeritz +* Purpose: +* Exercises the RSA encryption functions from the crypto library. +** +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace crypto; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace processes; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_RSA_CRYPTO + // uncomment for noisy run. + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) + +const int KEY_SIZE = 1024; + // the size of the RSA key that we'll create. + +const int MAX_STRING = 4000; + // the largest chunk that we'll try to encrypt. + +const int THREAD_COUNT = 5; // number of threads testing rsa at once. + +const int ITERATIONS = 6; // number of test runs in our testing threads. + +////////////// + +class test_rsa; // forward. + +class rsa_thread : public ethread +{ +public: + rsa_thread(test_rsa &parent) : ethread(), _parent(parent) {} + + void perform_activity(void *ptr); + // try out random rsa keys on randomly chosen chunks of the fodder. + +private: + test_rsa &_parent; +}; + +////////////// + +class test_rsa : public virtual unit_base, virtual public application_shell +{ +public: + test_rsa() + : _fodder(string_manipulation::make_random_name(MAX_STRING + 1, MAX_STRING + 1)) {} + virtual ~test_rsa() {} + DEFINE_CLASS_NAME("test_rsa"); + + const astring &fodder() const { return _fodder; } + + int execute(); + +private: + astring _fodder; // chunks taken from this are encrypted and decrypted. + time_stamp _program_start; // the time at which we started executing. + thread_cabinet _threads; // manages our testing threads. + friend class rsa_thread; // bad practice, but saves time in test app. +}; + +int test_rsa::execute() +{ + FUNCDEF("execute"); + int left = THREAD_COUNT; + while (left--) { + _threads.add_thread(new rsa_thread(*this), true, NIL); + } + + while (_threads.threads()) { +#ifdef DEBUG_RSA_CRYPTO + LOG(astring("cleaning debris.")); +#endif + _threads.clean_debris(); + time_control::sleep_ms(1000); + } + +#ifdef DEBUG_RSA_CRYPTO + int duration = int(time_stamp().value() - _program_start.value()); + LOG(a_sprintf("duration for %d keys and encrypt/decrypt=%d ms,", + ITERATIONS * THREAD_COUNT, duration)); + LOG(a_sprintf("that comes to %d ms per cycle.", int(double(duration + / ITERATIONS / THREAD_COUNT)))); +#endif + + return final_report(); +} + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) + +void rsa_thread::perform_activity(void *) +{ + FUNCDEF("perform_activity"); + int left = ITERATIONS; + while (left--) { + time_stamp start; + + rsa_crypto rc_private_here(KEY_SIZE); + int key_durat = int(time_stamp().value() - start.value()); + + byte_array public_key; + rc_private_here.public_key(public_key); // get our public portion. + byte_array private_key; + rc_private_here.private_key(private_key); // get our private portion. + +//RSA_print_fp(stdout, private_key, 0); +//RSA_print_fp(stdout, public_key, 0); + + int string_start = _parent.randomizer().inclusive(0, MAX_STRING); + int string_end = _parent.randomizer().inclusive(0, MAX_STRING); + flip_increasing(string_start, string_end); + astring ranstring = _parent.fodder().substring(string_start, string_end); + byte_array target; + + // the first phase tests the outsiders sending back data that only we, + // with our private key, can decrypt. + + start.reset(); + rsa_crypto rc_pub(public_key); + bool worked = rc_pub.public_encrypt(byte_array(ranstring.length() + 1, + (abyte*)ranstring.s()), target); + int pub_enc_durat = int(time_stamp().value() - start.value()); + ASSERT_TRUE(worked, "phase 1 shouldn't fail to encrypt the string"); + + rsa_crypto rc_priv(private_key); + byte_array recovered; + start.reset(); + worked = rc_priv.private_decrypt(target, recovered); + int priv_dec_durat = int(time_stamp().value() - start.value()); + ASSERT_TRUE(worked, "phase 1 should not fail to decrypt the string"); + + astring teddro = (char *)recovered.observe(); + + ASSERT_EQUAL(teddro, ranstring, "should not fail to get back the data"); + + // the second phase tests us using our private key to encrypt data which + // anyone with the public key can decode. + + start.reset(); + worked = rc_priv.private_encrypt(byte_array(ranstring.length() + 1, + (abyte*)ranstring.s()), target); + int priv_enc_durat = int(time_stamp().value() - start.value()); + ASSERT_TRUE(worked, "phase 2 should not fail to encrypt the string"); + + start.reset(); + worked = rc_pub.public_decrypt(target, recovered); + int pub_dec_durat = int(time_stamp().value() - start.value()); + ASSERT_TRUE(worked, "phase 2 should not fail to decrypt the string"); + + teddro = (char *)recovered.observe(); + + ASSERT_EQUAL(teddro, ranstring, "should not fail to get back the data here either"); + +#ifdef DEBUG_RSA_CRYPTO + LOG(a_sprintf("key generation: %d ms, public encrypt: %d ms, private " + "decrypt: %d ms", key_durat, pub_enc_durat, priv_dec_durat)); + LOG(a_sprintf("data size: %d bytes, private encrypt: %d ms, public " + "decrypt: %d ms", + string_end - string_start + 1, priv_enc_durat, pub_dec_durat)); +#endif + + time_control::sleep_ms(0); // take a rest. + } +} + +HOOPLE_MAIN(test_rsa, ) + diff --git a/nucleus/library/tests_filesystem/makefile b/nucleus/library/tests_filesystem/makefile new file mode 100644 index 00000000..b517463e --- /dev/null +++ b/nucleus/library/tests_filesystem/makefile @@ -0,0 +1,13 @@ +include cpp/variables.def + +PROJECT = tests_filesystem +TYPE = test +TARGETS = test_byte_filer.exe test_directory.exe test_directory_tree.exe test_file_info.exe \ + test_file_time.exe test_filename.exe test_huge_file.exe +DEFINITIONS += USE_HOOPLE_DLLS +LOCAL_LIBS_USED = unit_test application configuration filesystem loggers mathematics nodes \ + structures textual timely structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_filesystem/test_byte_filer.cpp b/nucleus/library/tests_filesystem/test_byte_filer.cpp new file mode 100644 index 00000000..8261d398 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_byte_filer.cpp @@ -0,0 +1,235 @@ +/*****************************************************************************\ +* * +* Name : test_byte_filer * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +class test_byte_filer : virtual public unit_base, virtual public application_shell +{ +public: + test_byte_filer() : application_shell() {} + DEFINE_CLASS_NAME("test_byte_filer"); + int run_simple_test(); + int run_file_scan(); + virtual int execute(); +}; + +const astring &TEST_FILE() +{ + const char *TEST_FILE_BASE = "/zz_garbage.txt"; + static astring __hidden_filename; + if (!__hidden_filename) { + __hidden_filename = environment::get("TMP"); + if (!__hidden_filename) __hidden_filename = "/tmp"; + __hidden_filename += astring(TEST_FILE_BASE); + } + return __hidden_filename; +} + +int test_byte_filer::run_simple_test() +{ + FUNCDEF("run_simple_test"); +#ifdef DEBUG + LOG("ahoy, beginning file test..."); + LOG(astring("test file is ") + TEST_FILE()); +#endif + + chaos randomizer; + +//hmmm: move to t_filename. + // test filename's exist operation. + byte_filer garbage(TEST_FILE().s(), "wb"); + garbage.write("oy.\n"); + garbage.close(); + filename test1(TEST_FILE()); + ASSERT_TRUE(test1.exists(), "exists test file should exist"); + filename test2("c:\\this_file_shouldNt_exist_ever.txt"); + ASSERT_FALSE(test2.exists(), "weird file should not existed"); + // test again to make sure it didn't create it. + ASSERT_FALSE(test2.exists(), "weird file should still not exist"); + test1.unlink(); + + int block_size = randomizer.inclusive(3000, 30000); +#ifdef DEBUG + LOG(a_sprintf("block size=%d", block_size)); +#endif + abyte *original_block = new abyte[block_size]; + for (int i = 0; i < block_size; i++) + original_block[i] = abyte(randomizer.inclusive(32, 126)); + unsigned int original_checksum + = checksums::bizarre_checksum((abyte *)original_block, block_size); + if (original_checksum) {} // compiler quieting. +#ifdef DEBUG + LOG(a_sprintf("random block checksum=%d", original_checksum)); +#endif + { + byte_array to_stuff_in_file(block_size, original_block); + delete [] original_block; + byte_filer fred(TEST_FILE(), "w+"); + fred.write(to_stuff_in_file); + } +#ifdef DEBUG + LOG(astring("about to compare file to checksum")); +#endif + { + abyte *temp_array = new abyte[21309]; + byte_array to_fake_stuff(21309, temp_array); + delete [] temp_array; + byte_filer fred(TEST_FILE(), "r"); +#ifdef DEBUG + LOG(astring("about to try writing to file")); +#endif + int should_be_failure = fred.write(to_fake_stuff); + ASSERT_EQUAL(should_be_failure, 0, "write on read only, should not succeed"); + +/// int fredsize = int(fred.size()); +/// fred.chunk_factor(fredsize); + +#ifdef DEBUG + LOG(a_sprintf("about to try reading from file %d bytes", fredsize)); +#endif + byte_array file_contents; + int bytes_read = fred.read(file_contents, block_size * 2); + ASSERT_EQUAL(bytes_read, block_size, "reading entire file should get proper size"); + un_int check_2 = checksums::bizarre_checksum((abyte *)file_contents.access(), file_contents.length()); + ASSERT_EQUAL((int)check_2, (int)original_checksum, "should read correct contents for checksum"); + } + +#define FACTOR 1354 + + { + int numpacs = number_of_packets(block_size, FACTOR); + byte_filer fred(TEST_FILE(), "rb"); +///file::READ_ONLY); +/// fred.chunk_factor(FACTOR); + int whole_size = 0; + for (int i = 0; i < numpacs; i++) { + byte_array blob_i; + int bytes_in = fred.read(blob_i, FACTOR); + ASSERT_FALSE(bytes_in > FACTOR, "we should never somehow read in more than we asked for"); + whole_size += blob_i.length(); + } + ASSERT_EQUAL(whole_size, fred.length(), "chunking comparison should see sizes as same"); + } + +// test writing out a copy and comparing them... there's no == on files! + + ASSERT_TRUE(filename(TEST_FILE()).unlink(), "cleanup should be able to remove temporary file"); + + // it seems everything worked during our tests. + return 0; +} + +int test_byte_filer::run_file_scan() +{ + FUNCDEF("run_file_scan"); + chaos randomizer; + + string_array files(_global_argc, (const char **)_global_argv); + files.zap(0, 0); // toss the first element since that's our app filename. + + if (!files.length()) { + // pretend they gave us the list of files in the TMP directory. some of + // these might fail if they're locked up. +// astring tmpdir = environment::get("TMP"); + astring tmpdir = application_configuration::current_directory(); + directory dir(tmpdir); + for (int i = 0; i < dir.files().length(); i++) { + // skip text files since we use those right here. + if ( (dir.files()[i].ends(".txt")) || (dir.files()[i].ends(".txt")) ) + continue; + astring chewed_string = tmpdir + "/" + dir.files()[i]; + files += chewed_string; + } +//LOG(astring("added files since no cmd args: ") + files.text_form()); + } + + byte_array data_found; + for (int i = 0; i < files.length(); i++) { + astring curr = files[i]; +// LOG(a_sprintf("file %d: ", i) + curr); + byte_filer test(curr, "rb"); + if (!test.good()) { + LOG(astring("good check: ") + curr + " cannot be opened. is this bad?"); + continue; + } + + // check that we get the expected position report from scooting to the + // end of a file. + test.seek(0, byte_filer::FROM_END); + ASSERT_EQUAL((int)test.tell(), (int)test.length(), "seek check should get to end as expected"); + test.seek(0, byte_filer::FROM_START); + + size_t len = test.length(); +//log(a_sprintf("file len is %.0f", double(len))); + size_t posn = 0; + while ( (posn < len) && !test.eof() ) { + size_t readlen = randomizer.inclusive(1, 256 * KILOBYTE); +//log(a_sprintf("read %u bytes, posn now %d bytes", readlen, posn)); + int bytes_read = int(test.read(data_found, int(readlen))); + ASSERT_TRUE(bytes_read >= 0, "reading should not fail to read some bytes"); + if (bytes_read > 0) { + posn += bytes_read; + } + } + ASSERT_TRUE(test.eof(), "eof check should see us at eof"); + ASSERT_EQUAL((int)posn, (int)len, "eof check should be at right position"); +// log(astring("successfully read ") + curr); + } + + return 0; +} + +int test_byte_filer::execute() +{ +// FUNCDEF("execute"); + int ret = run_simple_test(); + if (ret) return ret; // failed. + ret = run_file_scan(); + if (ret) return ret; // failed here. + + return final_report(); +} + +HOOPLE_MAIN(test_byte_filer, ) + diff --git a/nucleus/library/tests_filesystem/test_directory.cpp b/nucleus/library/tests_filesystem/test_directory.cpp new file mode 100644 index 00000000..e2e70165 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_directory.cpp @@ -0,0 +1,99 @@ +/* +* Name : test_directory +* Author : Chris Koeritz +* Purpose: +* Tests the directory object out to see if it scans properly. +** +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +////////////// + +class test_directory : public virtual unit_base, public virtual application_shell +{ +public: + test_directory() : application_shell() {} + DEFINE_CLASS_NAME("test_directory"); + int execute(); +}; + +////////////// + +int test_directory::execute() +{ + FUNCDEF("execute"); + { + astring path = "/tmp"; // default path. +#ifdef __WIN32__ + path = "c:/"; // default path for windoze. +#endif + if (application::_global_argc >= 2) + path = application::_global_argv[1]; + + astring pattern = "*"; + if (application::_global_argc >= 3) + pattern = application::_global_argv[2]; + +// log(astring("Scanning directory named \"") + path + "\""); +// log(astring("Using pattern-match \"") + pattern + "\""); + + directory dir(path, pattern.s()); + ASSERT_TRUE(dir.good(), "the current directory should be readable"); +// log(path + " contained these files:"); + astring names; + for (int i = 0; i < dir.files().length(); i++) { + names += dir.files()[i] + " "; + } + astring split; + string_manipulation::split_lines(names, split, 4); +// log(split); +// log(path + " contained these directories:"); + names = ""; + for (int i = 0; i < dir.directories().length(); i++) { + names += dir.directories()[i] + " "; + } + string_manipulation::split_lines(names, split, 4); +// log(split); + } +//hmmm: the above test proves zilch. +// it needs to do this differently. +// instead of relying on someone else's folder, pick and make our own. +// then fill it with some known stuff. +// verify then that the read form is identical! + + + +//more tests! + + return final_report(); +} + +HOOPLE_MAIN(test_directory, ) + diff --git a/nucleus/library/tests_filesystem/test_directory_tree.cpp b/nucleus/library/tests_filesystem/test_directory_tree.cpp new file mode 100644 index 00000000..c1894aca --- /dev/null +++ b/nucleus/library/tests_filesystem/test_directory_tree.cpp @@ -0,0 +1,207 @@ +/* +* Name : test_directory_tree +* Author : Chris Koeritz +* Purpose: +* Tests the directory_tree object on some well-known directories. +** +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +const bool JUST_SIZES = false; + // determines if we'll only compare file size and time. + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +class test_directory_tree : public virtual unit_base, virtual public application_shell +{ +public: + test_directory_tree() : application_shell() {} + DEFINE_CLASS_NAME("test_directory_tree"); + int execute(); +}; + +int test_directory_tree::execute() +{ + FUNCDEF("execute"); + + astring path = "/usr/include"; +#ifdef __WIN32__ + // default path for windoze uses an area that should always exist. + path = environment::get("COMMONPROGRAMFILES"); +#endif + + // process the command line parameters, which are optionally a directory name and + // a pattern to use when scanning. + if (_global_argc >= 2) + path = _global_argv[1]; + + astring pattern = "*"; + if (_global_argc >= 3) + pattern = _global_argv[2]; + + { +// log(astring("Scanning directory tree at \"") + path + "\""); +// log(astring("Using pattern-match \"") + pattern + "\""); + + directory_tree dir(path, pattern.s()); + ASSERT_TRUE(dir.good(), "directory_tree construction should succeed and be readable."); + + dir_tree_iterator *ted = dir.start(directory_tree::prefix); + // create our iterator to do a prefix traversal. + + int depth; // current depth in tree. + filename curr; // the current path the iterator is at. + string_array files; // the filenames held at the iterator. + + while (directory_tree::current(*ted, curr, files)) { + // we have a good directory to show. + directory_tree::depth(*ted, depth); +// log(string_manipulation::indentation(depth * 2) + astring("[") +// + curr.raw() + "]"); + astring names; + for (int i = 0; i < files.length(); i++) names += files[i] + " "; + if (names.length()) { + astring split; + string_manipulation::split_lines(names, split, depth * 2 + 2); +// log(split); + } + + // go to the next place. + directory_tree::next(*ted); + } + + directory_tree::throw_out(ted); + } + + { + // second test group. seek operation. +//scan the directory, create some temporary directories and junk filenames +//therein, then seek to that location. + + } + + { + // third test group. tree comparison operation. +// log(astring("Self-comparing directory tree at \"") + path + "\""); +// log(astring("Using pattern-match \"") + pattern + "\""); + +// LOG("reading tree 1."); + directory_tree dir(path, pattern.s()); + ASSERT_TRUE(dir.good(), "the directory should be readable for self-compare"); + + // now read a copy of the tree also. +// LOG("reading tree 2."); + directory_tree dir2(path, pattern.s()); + ASSERT_TRUE(dir2.good(), "the directory should read the second time fine too"); + +// LOG("comparing the two trees."); + filename_list diffs; + directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); +//LOG(diffs.text_form()); + + ASSERT_FALSE(diffs.elements(), "there should be no differences comparing identical dirs"); + } + + { + // fourth test: see if the calculate function works. +// log(astring("Calculating sums for tree at \"") + path + "\""); +// log(astring("Using pattern-match \"") + pattern + "\""); + +// LOG("reading tree 1."); + directory_tree dir(path, pattern.s()); + ASSERT_TRUE(dir.good(), "the directory should be readable for checksums"); + + // now read a copy of the tree also. +// LOG("reading tree 2."); + directory_tree dir2(path, pattern.s()); + ASSERT_TRUE(dir2.good(), "checksummer should be able to read second time also"); + +// LOG("calculating checksums for tree 1."); + ASSERT_TRUE(dir.calculate(JUST_SIZES), "the first checksummer tree can be calculated"); + +// LOG("calculating checksums for tree 2."); + ASSERT_TRUE(dir2.calculate(JUST_SIZES), "the second checksummer tree can be calculated"); + +// LOG("comparing the two trees."); + filename_list diffs; + directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); +//LOG(diffs.text_form()); + + ASSERT_FALSE(diffs.elements(), "no checksummer differences should be seen for identical directories"); + } + + { + // fifth test: see if the packing works. +// log(astring("Reading tree for packing at \"") + path + "\""); +// log(astring("Using pattern-match \"") + pattern + "\""); + +// LOG("reading tree."); + directory_tree dir(path, pattern.s()); + ASSERT_TRUE(dir.good(), "packer directory should be read"); + +// LOG("calculating checksums for tree."); + ASSERT_TRUE(dir.calculate(JUST_SIZES), "the first packer tree can be calculated"); + + byte_array packed_form; + int size_packed = dir.packed_size(); + dir.pack(packed_form); +//LOG(a_sprintf("tree became %d abyte array", packed_form.length())); + ASSERT_EQUAL(size_packed, packed_form.length(), "packed size should be right"); + + directory_tree dir2; + ASSERT_TRUE(dir2.unpack(packed_form), "second tree can be unpacked from the first"); + +// LOG("comparing the two trees."); + filename_list diffs; + directory_tree::compare_trees(dir, dir2, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); +//LOG(diffs.text_form()); + + ASSERT_FALSE(diffs.elements(), "identical directories should stay same after packing"); + + directory_tree::compare_trees(dir2, dir, diffs, file_info::EQUAL_CHECKSUM_TIMESTAMP_FILESIZE); + ASSERT_FALSE(diffs.elements(), "no differences for reverse compare identical dirs"); + } + +// nth test: +// combine the results of the second test with a comparison like in the +// third test. delete all of those temporary files that were added. +// rescan tree. make sure that a tree containing the temporaries +// when compared with the current post-deletion tree produces a list +// that contains all the temporary files and directories. + + +//hmmm: more tests! + + return final_report(); +} + +HOOPLE_MAIN(test_directory_tree, ) + diff --git a/nucleus/library/tests_filesystem/test_file_info.cpp b/nucleus/library/tests_filesystem/test_file_info.cpp new file mode 100644 index 00000000..cc0a41d2 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_file_info.cpp @@ -0,0 +1,111 @@ +/* +* Name : test_file_info +* Author : Chris Koeritz +* Copyright (c) 1991-$now By Author. This program is free software; you can * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_TEST_FILE_INFO + // uncomment for noisy version. + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +static chaos a_randomizer; + +////////////// + +class test_file_info : public application_shell, public unit_base +{ +public: + test_file_info() : application_shell(), unit_base() {} + + DEFINE_CLASS_NAME("test_file_info"); + + virtual int execute(); +}; + +////////////// + +//hmmm: stolen from ssl_init. +byte_array random_bytes(int length) +{ + byte_array seed; + for (int i = 0; i < length; i++) + seed += abyte(chaos().inclusive(0, 255)); + return seed; +} + +int test_file_info::execute() +{ + FUNCDEF("execute"); +#ifdef __UNIX__ + file_time absurdity_time("/"); +#endif +#ifdef __WIN32__ + file_time absurdity_time("c:/"); +#endif + + // test storing info via the constructor. + file_info testing(filename("/usr/schrodingers/dog/got/away"), 7298238); + testing._time = absurdity_time; + testing._checksum = 1283412; + ASSERT_EQUAL((int)testing._file_size, (int)7298238, "constructor file size"); + ASSERT_EQUAL(testing._time, absurdity_time, "constructor file time"); + ASSERT_EQUAL(testing._checksum, 1283412, "constructor checksum"); + ASSERT_EQUAL((filename &)testing, filename("/usr/schrodingers/dog/got/away"), + "constructor filename"); + + // test packing the object and packed_size. + byte_array packed; + int size = testing.packed_size(); + testing.pack(packed); + ASSERT_EQUAL(size, packed.length(), "basic packed size accuracy"); + file_info unstuffy; + ASSERT_TRUE(unstuffy.unpack(packed), "basic unpacking"); + + // test validity after unpacking. + ASSERT_EQUAL((int)unstuffy._file_size, (int)7298238, "constructor file size"); + ASSERT_EQUAL(unstuffy._time, absurdity_time, "constructor file time"); + ASSERT_EQUAL(unstuffy._checksum, 1283412, "constructor checksum"); + ASSERT_EQUAL((filename &)unstuffy, filename("/usr/schrodingers/dog/got/away"), + "constructor filename"); + + // test the extra bits, the attachment and secondary name. + astring seconame = "glorabahotep"; + testing.secondary(seconame ); + const byte_array randobytes = random_bytes(chaos().inclusive(37, 4128)); + testing.attachment(randobytes); + packed.reset(); + size = testing.packed_size(); + testing.pack(packed); + ASSERT_EQUAL(size, packed.length(), "secondary packed size accuracy"); + ASSERT_TRUE(unstuffy.unpack(packed), "secondary unpacking"); + // test that the secondary name and attachment came back. + ASSERT_EQUAL(seconame, unstuffy.secondary(), "secondary name incorrect"); + ASSERT_EQUAL(randobytes, unstuffy.attachment(), "secondary attachment inaccurate"); + + return final_report(); +} + +HOOPLE_MAIN(test_file_info, ) + diff --git a/nucleus/library/tests_filesystem/test_file_time.cpp b/nucleus/library/tests_filesystem/test_file_time.cpp new file mode 100644 index 00000000..6e79f9a2 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_file_time.cpp @@ -0,0 +1,107 @@ +/* +* Name : test_file_time +* Author : Chris Koeritz +* Copyright (c) 1991-$now By Author. This program is free software; you can * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include +#endif + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_TEST_FILE_INFO + // uncomment for noisy version. + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +static chaos a_randomizer; + +////////////// + +class test_file_time : public application_shell, public unit_base +{ +public: + test_file_time() : application_shell(), unit_base() {} + + DEFINE_CLASS_NAME("test_file_time"); + + virtual int execute(); +}; + +////////////// + +int test_file_time::execute() +{ + FUNCDEF("execute"); + +#ifdef __UNIX__ + astring toppy("/"); +#endif +#ifdef __WIN32__ + astring toppy("c:/ntldr"); // will work for any modern windows OS. +#endif + + // test storing info via the constructor. + file_time absurdity_time(toppy); + FILE *topdir = fopen(toppy.s(), "r"); + file_time nutty_time(topdir); + struct stat sbuffer; + int filenum = fileno(topdir); + int stat_okay = fstat(filenum, &sbuffer); + ASSERT_FALSE(stat_okay, "failure to read filetime"); + file_time goofy_time(sbuffer.st_mtime); + fclose(topdir); + file_time testing(goofy_time); // copy ctor. + // test that they all got the same idea from the file. + ASSERT_EQUAL(absurdity_time, nutty_time, "filename vs. FILE ctor"); + ASSERT_EQUAL(absurdity_time, goofy_time, "filename vs. time_t ctor"); + ASSERT_EQUAL(absurdity_time, testing, "filename vs. copy ctor"); + ASSERT_EQUAL(nutty_time, goofy_time, "FILE vs. time_t ctor"); + ASSERT_EQUAL(nutty_time, testing, "FILE vs. copy ctor"); + // one reversed direction check. + ASSERT_EQUAL(goofy_time, absurdity_time, "time_t vs. filename ctor"); + + // test packing the object and packed_size. + byte_array packed; + int size = testing.packed_size(); + testing.pack(packed); + ASSERT_EQUAL(size, packed.length(), "packed size accuracy"); + file_time unstuffy; + ASSERT_TRUE(unstuffy.unpack(packed), "unpacking"); + ASSERT_EQUAL((double)testing.raw(), (double)unstuffy.raw(), "unpacked contents should be equal to prior"); + + // test the text_form method. + astring text; + testing.text_form(text); + ASSERT_INEQUAL(text.length(), 0, "text_form produces text"); + + // test validity after unpacking. + ASSERT_EQUAL(unstuffy, goofy_time, "constructor file size"); + + return final_report(); +} + +HOOPLE_MAIN(test_file_time, ) + diff --git a/nucleus/library/tests_filesystem/test_filename.cpp b/nucleus/library/tests_filesystem/test_filename.cpp new file mode 100644 index 00000000..8f08de45 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_filename.cpp @@ -0,0 +1,226 @@ +/* +* Name : test_filename +* Author : Chris Koeritz +** +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#define DEBUG_FILENAME_TEST + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +class test_filename : virtual public unit_base, public virtual application_shell +{ +public: + test_filename() : application_shell() {} + DEFINE_CLASS_NAME("test_filename"); + virtual int execute(); + void clean_sequel(astring &sequel); +}; + +void test_filename::clean_sequel(astring &sequel) +{ sequel.replace_all('\\', '/'); } + +int test_filename::execute() +{ + FUNCDEF("execute") + { + // first test group. + filename gorgeola(""); + ASSERT_FALSE(gorgeola.exists(), "an empty filename should not exist"); + } + + { + // second test group. + astring GROUP = "separate-- "; + filename turkey("/omega/ralph/turkey/buzzard.txt"); + string_array pieces; + turkey.separate(pieces); + ASSERT_TRUE(pieces[1].equal_to("omega"), GROUP + "the first piece didn't match."); + ASSERT_TRUE(pieces[2].equal_to("ralph"), GROUP + "the second piece didn't match."); + ASSERT_TRUE(pieces[3].equal_to("turkey"), GROUP + "the third piece didn't match."); + ASSERT_TRUE(pieces[4].equal_to("buzzard.txt"), GROUP + "the fourth piece didn't match."); + ASSERT_EQUAL(pieces.length(), 5, GROUP + "the list was the wrong length"); + } + + { + // third test group. + astring GROUP = "third: test compare_prefix "; + filename turkey("/omega/ralph/turkey/buzzard.txt"); + filename murpin1("/omega"); + filename murpin2("/omega/ralph"); + filename murpin3("/omega/ralph/turkey"); + filename murpin4("/omega/ralph/turkey/buzzard.txt"); + filename murpin_x1("ralph/turkey/buzzard.txt"); + filename murpin_x2("/omega/ralph/turkey/buzzard.txt2"); + filename murpin_x3("/omega/turkey/buzzard.txt"); + filename murpin_x4("/omega/ralph/turkey/b0/buzzard.txt"); + filename murpin_x5("moomega/ralph/turkey"); + + astring sequel; + ASSERT_TRUE(murpin1.compare_prefix(turkey, sequel), GROUP + "first should match but didn't"); + clean_sequel(sequel); + ASSERT_TRUE(sequel.equal_to("ralph/turkey/buzzard.txt"), GROUP + "first sequel was wrong"); + ASSERT_TRUE(murpin2.compare_prefix(turkey, sequel), GROUP + "second should match but didn't"); + clean_sequel(sequel); + ASSERT_TRUE(sequel.equal_to("turkey/buzzard.txt"), GROUP + "second sequel was wrong"); + ASSERT_TRUE(murpin3.compare_prefix(turkey, sequel), GROUP + "third should match but didn't"); + clean_sequel(sequel); + ASSERT_TRUE(sequel.equal_to("buzzard.txt"), GROUP + "third sequel was wrong"); + ASSERT_TRUE(murpin4.compare_prefix(turkey, sequel), GROUP + "fourth should match but didn't"); + ASSERT_FALSE(sequel.t(), GROUP + "fourth had a sequel but shouldn't"); + + ASSERT_FALSE(murpin_x1.compare_prefix(turkey, sequel), + GROUP + "x-first should not match but did"); + ASSERT_FALSE(sequel.t(), + GROUP + "x-first had a sequel but shouldn't"); + ASSERT_FALSE(murpin_x2.compare_prefix(turkey, sequel), + GROUP + "x-second should not match but did"); + ASSERT_FALSE(sequel.t(), + GROUP + "x-second had a sequel but shouldn't"); + ASSERT_FALSE(murpin_x3.compare_prefix(turkey, sequel), + GROUP + "x-third should not match but did"); + ASSERT_FALSE(sequel.t(), + GROUP + "x-third had a sequel but shouldn't"); + ASSERT_FALSE(murpin_x4.compare_prefix(turkey, sequel), + GROUP + "x-fourth should not match but did"); + ASSERT_FALSE(sequel.t(), + GROUP + "x-fourth had a sequel but shouldn't"); + ASSERT_FALSE(murpin_x5.compare_prefix(turkey, sequel), + GROUP + "x-fifth should not match but did"); + ASSERT_FALSE(sequel.t(), + GROUP + "x-fifth had a sequel but shouldn't"); + + // check that the functions returning no sequel are still correct. + ASSERT_TRUE(murpin1.compare_prefix(turkey), GROUP + "the two versions differed!"); + ASSERT_FALSE(murpin_x1.compare_prefix(turkey), GROUP + "x-the two versions differed!"); + } + + { + // fourth test group. + astring GROUP = "fourth: test compare_suffix "; + filename turkey("/omega/ralph/turkey/buzzard.txt"); + filename murpin1("turkey\\buzzard.txt"); + filename murpin2("turkey/buzzard.txt"); + filename murpin3("ralph/turkey/buzzard.txt"); + filename murpin4("omega/ralph/turkey/buzzard.txt"); + filename murpin5("/omega/ralph/turkey/buzzard.txt"); + + ASSERT_TRUE(murpin1.compare_suffix(turkey), GROUP + "compare 1 failed"); + ASSERT_TRUE(murpin2.compare_suffix(turkey), GROUP + "compare 2 failed"); + ASSERT_TRUE(murpin3.compare_suffix(turkey), GROUP + "compare 3 failed"); + ASSERT_TRUE(murpin4.compare_suffix(turkey), GROUP + "compare 4 failed"); + ASSERT_TRUE(murpin5.compare_suffix(turkey), GROUP + "compare 5 failed"); + + ASSERT_FALSE(turkey.compare_suffix(murpin1), GROUP + "compare x.1 failed"); + } + + { + // fifth test group. + // tests out the canonicalization method on any parameters given on + // the command line, including the program name. + astring GROUP = "fifth: canonicalize command-line paths "; +// log(GROUP, ALWAYS_PRINT); + for (int i = 0; i < application::_global_argc; i++) { + filename canony(application::_global_argv[i]); +// log(a_sprintf("parm %d:\n\tfrom \"%s\"\n\t to \"%s\"", i, application::_global_argv[i], +// canony.raw().s()), ALWAYS_PRINT); + +//hmmm: the above wasn't really a test so much as a look at what we did. +// we should run the canonicalizer against a set of known paths so we can know what to +// expect. + + } + } + + { + // sixth test group. + astring GROUP = "sixth: testing pop and push "; + // test dossy paths. + filename test1("c:/flug/blumen/klemper/smooden"); +//log(astring("base=") + test1.basename(), ALWAYS_PRINT); + ASSERT_EQUAL(test1.basename(), astring("smooden"), GROUP + "basename 1 failed"); +//log(astring("got past basename 1 test that was failing.")); + ASSERT_EQUAL(test1.dirname(), filename("c:/flug/blumen/klemper"), + GROUP + "d-dirname 1 failed"); +//log(astring("got past a test or so after that.")); + filename test2 = test1; + astring popped = test2.pop(); + ASSERT_EQUAL(popped, astring("smooden"), GROUP + "dpop 1 return failed"); + ASSERT_EQUAL(test2, filename("c:/flug/blumen/klemper"), GROUP + "dpop 1 failed"); + test2.pop(); + test2.pop(); + ASSERT_EQUAL(test2, filename("c:/flug"), GROUP + "dpop 2 failed"); + popped = test2.pop(); + ASSERT_EQUAL(popped, astring("flug"), GROUP + "dpop 1 return failed"); + ASSERT_EQUAL(test2, filename("c:/"), GROUP + "dpop 3 failed"); + test2.pop(); + ASSERT_EQUAL(test2, filename("c:/"), GROUP + "dpop 3 failed"); + test2.push("flug"); + test2.push("blumen"); + test2.push("klemper"); + ASSERT_EQUAL(test2, filename("c:/flug/blumen/klemper"), GROUP + "dpush 1 failed"); + // test unix paths. + filename test3("/flug/blumen/klemper/smooden"); + ASSERT_EQUAL(test3.basename(), astring("smooden"), GROUP + "basename 1 failed"); + ASSERT_EQUAL(test3.dirname(), filename("/flug/blumen/klemper"), + GROUP + "u-dirname 1 failed"); + filename test4 = test3; + popped = test4.pop(); + ASSERT_EQUAL(popped, astring("smooden"), GROUP + "upop 1 return failed"); + ASSERT_EQUAL(test4, filename("/flug/blumen/klemper"), GROUP + "upop 1 failed"); + test4.pop(); + test4.pop(); + ASSERT_EQUAL(test4, filename("/flug"), GROUP + "upop 2 failed"); + popped = test4.pop(); + ASSERT_EQUAL(popped, astring("flug"), GROUP + "upop 1 return failed"); + ASSERT_EQUAL(test4, filename("/"), GROUP + "upop 3 failed"); + test4.pop(); + ASSERT_EQUAL(test4, filename("/"), GROUP + "upop 3 failed"); + test4.push("flug"); + test4.push("blumen"); + test4.push("klemper"); + ASSERT_EQUAL(test4, filename("/flug/blumen/klemper"), GROUP + "upush 1 failed"); + } + { + // seventh test group. + astring GROUP = "seventh: testing pack and unpack "; + filename test1("/usr/local/athabasca"); + byte_array packed; + int size_guess = test1.packed_size(); + test1.pack(packed); + ASSERT_EQUAL(size_guess, packed.length(), GROUP + "packed_size 1 failed"); + filename test2; + ASSERT_TRUE(test2.unpack(packed), GROUP + "unpack 1 failed"); + ASSERT_EQUAL(test2, test1, GROUP + "packed contents differ, 1 failed"); + } + + return final_report(); +} + +HOOPLE_MAIN(test_filename, ) + diff --git a/nucleus/library/tests_filesystem/test_huge_file.cpp b/nucleus/library/tests_filesystem/test_huge_file.cpp new file mode 100644 index 00000000..f9209eb7 --- /dev/null +++ b/nucleus/library/tests_filesystem/test_huge_file.cpp @@ -0,0 +1,110 @@ +/* +* Name : test_huge_file +* Author : Chris Koeritz +** +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) + +class test_huge_file : public virtual unit_base, virtual public application_shell +{ +public: + test_huge_file() : application_shell() {} + DEFINE_CLASS_NAME("test_huge_file"); + void run_file_scan(); + virtual int execute(); +}; + +void test_huge_file::run_file_scan() +{ + FUNCDEF("run_file_scan"); + chaos randomizer; + + string_array files(application::_global_argc, (const char **)application::_global_argv); + files.zap(0, 0); // toss the first element since that's our app filename. + + if (!files.length()) { + // pretend they gave us the list of files in the TMP directory. some of + // these might fail if they're locked up. +// astring tmpdir = environment::get("TMP"); + astring tmpdir = application_configuration::current_directory(); + directory dir(tmpdir); + for (int i = 0; i < dir.files().length(); i++) { + // skip text files since we use those right here. + if (dir.files()[i].ends(".txt")) + continue; + astring chewed_string = tmpdir + "/" + dir.files()[i]; + files += chewed_string; + } +//LOG(astring("added files since no cmd args: ") + files.text_form()); + } + + byte_array data_found; + for (int i = 0; i < files.length(); i++) { + astring curr = files[i]; +// LOG(a_sprintf("file %d: ", i) + curr); + huge_file test(curr, "rb"); + ASSERT_TRUE(test.good(), "good check should say yes, it's good."); + double len = test.length(); +//log(a_sprintf("file len is %.0f", len)); + double posn = 0; + while ( (posn < len) && !test.eof() ) { + int readlen = randomizer.inclusive(1, 256 * KILOBYTE); +//log(a_sprintf("read %.0f bytes, posn now %.0f bytes", double(readlen), posn)); + int bytes_read = 0; + outcome ret = test.read(data_found, readlen, bytes_read); + ASSERT_EQUAL(ret.value(), huge_file::OKAY, "should be able to read file"); + if (ret == huge_file::OKAY) { + posn += bytes_read; + } + } + ASSERT_TRUE(test.eof(), "eof check should be at eof."); + if (posn != len) + log(a_sprintf("failed check, want %.0f, got %.0f", double(len), double(posn))); + ASSERT_EQUAL(posn, len, "eof check should be at right position: "); +// log(astring("successfully read ") + curr); + } +} + +int test_huge_file::execute() +{ + FUNCDEF("execute"); + run_file_scan(); + return final_report(); +} + +HOOPLE_MAIN(test_huge_file, ) + diff --git a/nucleus/library/tests_mathematics/makefile b/nucleus/library/tests_mathematics/makefile new file mode 100644 index 00000000..bef10eb2 --- /dev/null +++ b/nucleus/library/tests_mathematics/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = tests_mathematics +TYPE = test +TARGETS = test_chaos.exe test_double_plus.exe test_math_ops.exe +DEFINITIONS += USE_HOOPLE_DLLS +LOCAL_LIBS_USED = unit_test application configuration filesystem loggers mathematics nodes \ + structures processes textual timely structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_mathematics/test_chaos.cpp b/nucleus/library/tests_mathematics/test_chaos.cpp new file mode 100644 index 00000000..7be780f4 --- /dev/null +++ b/nucleus/library/tests_mathematics/test_chaos.cpp @@ -0,0 +1,99 @@ +/* +* Name : test_chaos +* Author : Chris Koeritz +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +//#define DEBUG_CHAOS + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define MAX_RANDOM_BINS 40 +#define MAX_TEST_CYCLES 10008 +#define AVG_EXPECTED_PER_BIN (double(MAX_TEST_CYCLES) / double(MAX_RANDOM_BINS)) +#define VARIATION_ALLOWED (AVG_EXPECTED_PER_BIN * 0.1) +#define ANOMALIES_ALLOWED (MAX_RANDOM_BINS / 4) + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +class test_chaos : virtual public unit_base, virtual public application_shell +{ +public: + test_chaos() : application_shell() {} + DEFINE_CLASS_NAME("test_chaos"); + virtual int execute(); +}; + +int test_chaos::execute() +{ + FUNCDEF("execute"); +#ifdef DEBUG_CHAOS + LOG(a_sprintf("average expected=%f, variation allowed=%f", + AVG_EXPECTED_PER_BIN, VARIATION_ALLOWED)); +#endif + int results[MAX_RANDOM_BINS]; + for (int k = 0; k < MAX_RANDOM_BINS; k++) results[k] = 0; + chaos randomizer; + + for (int i = 0; i < MAX_TEST_CYCLES; i++) { + // first test if exclusivity is ensured... + int res = randomizer.exclusive(0, MAX_RANDOM_BINS - 1); + ASSERT_FALSE( (res <= 0) || (res >= MAX_RANDOM_BINS - 1), + "exclusive test should not go out of bounds"); + // then test for our statistics. + int base = randomizer.inclusive(-1000, 1000); + // pick a base for the number below. + res = randomizer.inclusive(base, base + MAX_RANDOM_BINS - 1); + ASSERT_FALSE( (res < base) || (res > base + MAX_RANDOM_BINS - 1), + "inclusive test should not go out of bounds"); +//LOG(a_sprintf("adding it to %d bin", res - base)); + results[res - base]++; + } +#ifdef DEBUG_CHAOS + LOG("Anomalies:"); +#endif + int failed_any = false; + for (int j = 0; j < MAX_RANDOM_BINS; j++) { + if (absolute_value(results[j] - AVG_EXPECTED_PER_BIN) > VARIATION_ALLOWED) { + failed_any++; +#ifdef DEBUG_CHAOS + LOG(astring(astring::SPRINTF, "%d: difference=%f", + j, double(results[j] - AVG_EXPECTED_PER_BIN))); +#endif + } + } +#ifdef DEBUG_CHAOS + if (!failed_any) LOG("None") + else LOG(a_sprintf("Saw %d anomalies of %d allowed.", failed_any, ANOMALIES_ALLOWED)); +#endif + + ASSERT_FALSE(failed_any > ANOMALIES_ALLOWED, + "probability anomalies should be less than the allowed number"); + return final_report(); +} + +HOOPLE_MAIN(test_chaos, ) + diff --git a/nucleus/library/tests_mathematics/test_double_plus.cpp b/nucleus/library/tests_mathematics/test_double_plus.cpp new file mode 100644 index 00000000..df216d9c --- /dev/null +++ b/nucleus/library/tests_mathematics/test_double_plus.cpp @@ -0,0 +1,69 @@ +/* +* Name : test_double_plus +* Author : Chris Koeritz +* Purpose: Tests the double_plus class out. +** +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace geometric; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace unit_test; + +typedef double_plus floot; + +class test_double_plus : public virtual unit_base, public virtual application_shell +{ +public: + test_double_plus() : application_shell() {} + DEFINE_CLASS_NAME("test_double_plus"); + virtual int execute(); +}; + +int test_double_plus::execute() +{ + FUNCDEF("execute"); + floot x1 = 43.8106392325; + floot x2 = 43.8106; + ASSERT_EQUAL(x1, x2, "these doubles should be close enough"); + + floot y1 = 16.78; + floot y2 = 16.798273773; + ASSERT_INEQUAL(y1, y2, "these doubles shouldn't be close enough"); + + floot z1(16.8, 0.1); + floot z2(16.798273773, 0.1); + ASSERT_EQUAL(a_sprintf("%.3f", z2.truncate()), astring("16.800"), + "truncate should calculate proper string"); + ASSERT_EQUAL(z1, z2, "these doubles should be close enough with short delta"); + + floot q1(16.75, 0.01); + floot q2(16.749273773, 0.01); + ASSERT_EQUAL(a_sprintf("%.3f", q2.truncate()), astring("16.750"), + "wider truncate should calculate proper string"); + ASSERT_EQUAL(q1, q2, "next couple doubles should be close enough with small delta"); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_double_plus, ) + diff --git a/nucleus/library/tests_mathematics/test_math_ops.cpp b/nucleus/library/tests_mathematics/test_math_ops.cpp new file mode 100644 index 00000000..19f966a1 --- /dev/null +++ b/nucleus/library/tests_mathematics/test_math_ops.cpp @@ -0,0 +1,60 @@ +/*****************************************************************************\ +* * +* Name : test_math_ops * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +//using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +class test_math_ops : virtual public unit_base, virtual public application_shell +{ +public: + test_math_ops() {} + DEFINE_CLASS_NAME("test_math_ops"); + virtual int execute(); +}; + +////////////// + +int test_math_ops::execute() +{ + FUNCDEF("execute"); + // test one: make sure factorial is working. + basis::un_int fact3 = math_ops::factorial(3); + ASSERT_EQUAL(fact3, 6, "3! did not equal 6"); + basis::un_int fact8 = math_ops::factorial(8); + ASSERT_EQUAL(fact8, 40320, "8! did not equal 40320"); + basis::un_int fact10 = math_ops::factorial(10); + ASSERT_EQUAL(fact10, 3628800, "10! did not equal 3628800"); + + return final_report(); +} + +HOOPLE_MAIN(test_math_ops, ) + diff --git a/nucleus/library/tests_nodes/makefile b/nucleus/library/tests_nodes/makefile new file mode 100644 index 00000000..30ddb492 --- /dev/null +++ b/nucleus/library/tests_nodes/makefile @@ -0,0 +1,11 @@ +include cpp/variables.def + +PROJECT = tests_node +TYPE = test +TARGETS = test_list.exe test_node.exe test_packable_tree.exe test_symbol_tree.exe test_tree.exe +LOCAL_LIBS_USED = unit_test application nodes loggers processes filesystem configuration timely textual structures basis +DEFINITIONS += USE_HOOPLE_DLLS +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_nodes/test_list.cpp b/nucleus/library/tests_nodes/test_list.cpp new file mode 100644 index 00000000..14a4fc7b --- /dev/null +++ b/nucleus/library/tests_nodes/test_list.cpp @@ -0,0 +1,140 @@ +/*****************************************************************************\ +* * +* Name : test_list * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace mathematics; +using namespace nodes; +using namespace structures; +using namespace unit_test; + +//#define DEBUG_LIST + // uncomment this line to get more debugging output. + +const int DEFAULT_ITERATIONS = 50; + // the default number of times we run through our phase loop. + +typedef basket t_node; + // the object we store in the list, a templated integer. + +#define CASTER(bare_node) static_cast(bare_node) + // turns a node pointer into our special t_node. + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) + +////////////// + +class test_list : virtual public unit_base, virtual public application_shell +{ +public: + test_list() : unit_base() {} + DEFINE_CLASS_NAME("test_list"); + virtual int execute(); +}; + +HOOPLE_MAIN(test_list, ); + +////////////// + +int test_list::execute() +{ + FUNCDEF("execute"); + + list the_list; + chaos randomizer; + + int iterations_left = DEFAULT_ITERATIONS; + while (iterations_left-- > 0) { + + // run through the phases below as many times as we are told. + + { + // phase for adding a random number into the list. + int to_add = randomizer.inclusive(0, 100000); + + // seek the correct insertion place to keep the list ordered. + list::iterator iter = the_list.head(); + while (!iter.is_tail() && iter.observe() + && (CASTER(iter.observe())->stored() <= to_add) ) + iter++; + the_list.insert(iter, new t_node(2, to_add)); + } + + { + // test the list invariant (which is that all elements should be sorted + // in non-decreasing order). + list::iterator iter = the_list.tail(); + // initialize our comparator. + int bigger = CASTER(iter.observe())->stored(); + // loop backwards until we hit the head. + while (!iter.is_head()) { + // check that the last value is not less than the current value. + ASSERT_FALSE(bigger < CASTER(iter.observe())->stored(), + "invariant check should not find a mal-ordering in the list"); + bigger = CASTER(iter.observe())->stored(); + iter--; + } + } + + { + // if the conditions are favorable, we whack at least one element out of + // the list. + if (randomizer.inclusive(1, 100) < 20) { + int elem = the_list.elements(); + int to_whack = randomizer.inclusive(0, elem - 1); + + // start at the head of the list... + list::iterator iter = the_list.head(); + // and jump to the element we chose. + the_list.forward(iter, to_whack); + ASSERT_EQUAL(the_list.index(iter), to_whack, + "forward should not see logic error where index of element to zap is incorrect"); + ASSERT_FALSE(iter.is_tail(), + "forward should not see logic error where we get to the tail somehow"); + the_list.zap(iter); + } + } + + } + +#ifdef DEBUG_LIST + list::iterator iter = the_list.head(); + log(astring("")); + log(astring("list contents:")); + int indy = 0; + while (!iter.is_tail()) { + int item = CASTER(iter.observe())->stored(); + log(a_sprintf("item #%d: %d", indy, item)); + indy++; + iter++; + } +#endif + + return final_report(); +} + + diff --git a/nucleus/library/tests_nodes/test_node.cpp b/nucleus/library/tests_nodes/test_node.cpp new file mode 100644 index 00000000..82dfb77c --- /dev/null +++ b/nucleus/library/tests_nodes/test_node.cpp @@ -0,0 +1,87 @@ +/*****************************************************************************\ +* * +* Name : t_node * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Tests out the node base class. * +* * +******************************************************************************* +* Copyright (c) 1989-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//hmmm: make this a more aggressive and realistic test. try implementing +// some list algorithms or graph algorithms to push node around. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace nodes; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +class test_node : public virtual unit_base, public virtual application_shell +{ +public: + test_node() {} + DEFINE_CLASS_NAME("test_node"); + virtual int execute(); +}; + +void bogon(byte_array *fred) +{ + if (fred) LOG("eep") + else LOG("eek"); +} + +int test_node::execute() +{ + FUNCDEF("execute"); + + byte_array blank; + basket fred(2, blank); + basket george(2, blank); + basket f_end1(0); + basket f_end2(0); + basket g_end1(0); + basket g_end2(0); + + node root; + + // add some links to the linkless root. + root.insert_link(0, &fred); + root.insert_link(1, &george); + + // set the pre-existing links to our end points. + fred.set_link(0, &f_end1); + fred.set_link(1, &f_end2); + george.set_link(0, &g_end1); + george.set_link(1, &g_end2); + + return final_report(); +} + +HOOPLE_MAIN(test_node, ); + diff --git a/nucleus/library/tests_nodes/test_packable_tree.cpp b/nucleus/library/tests_nodes/test_packable_tree.cpp new file mode 100644 index 00000000..3d971dda --- /dev/null +++ b/nucleus/library/tests_nodes/test_packable_tree.cpp @@ -0,0 +1,204 @@ +////////////// +// Name : test_packable_tree +// Author : Chris Koeritz +////////////// +// Copyright (c) 1992-$now By Author. 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: +// http://www.gnu.org/licenses/gpl.html +// or under the terms of the GNU Library license: +// http://www.gnu.org/licenses/lgpl.html +// at your preference. Those licenses describe your legal rights to this +// software, and no other rights or warranties apply. +// Please send updates for this code to: fred@gruntose.com -- Thanks, fred. +////////////// + +//! tests some critical properties for the packable tree. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace nodes; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_PACKABLE_TREE + // set this to enable debugging features of the string class. + +//HOOPLE_STARTUP_CODE; + +//#define DEBUG_PACKABLE_TREE_TEST + // uncomment for testing version. + +#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) + +#define WHERE __WHERE__.s() + +#define FUNKIT(str) basis::a_sprintf("%s: %s", func, basis::astring(str).s()) + +// test: reports an error if the condition evaluates to non-zero. +int compnum = 0; +const float TEST_RUNTIME_DEFAULT = .02 * MINUTE_ms; + // the test, by default, will run for this long. + +////////////// + +class test_packable_tree : public application_shell, public unit_base +{ +public: + test_packable_tree() {} + ~test_packable_tree() {} + + DEFINE_CLASS_NAME("test_packable_tree"); + + virtual int execute() { + run_test(); + return final_report(); + } + + void run_test(); +}; + +HOOPLE_MAIN(test_packable_tree, ) + +////////////// + +//! it's not the one tree (c). this is just a derived packable_tree we can test with. + +class many_tree : public packable_tree +{ +public: + many_tree(const file_info &inf) : c_inf(new file_info(inf)) {} + + virtual ~many_tree() { WHACK(c_inf); } + + file_info get_info() const { return *c_inf; } + + virtual int packed_size() const { + return c_inf->packed_size(); + } + + virtual void pack(basis::byte_array &packed_form) const { + c_inf->pack(packed_form); + } + + virtual bool unpack(basis::byte_array &packed_form) { + if (!c_inf->unpack(packed_form)) return false; +//other pieces? + return true; + } + +private: + file_info *c_inf; +}; + +////////////// + +//! the factory that creates our special type of tree. + +class tree_defacto : public packable_tree_factory +{ +public: + packable_tree *create() { return new many_tree(file_info()); } +}; + +////////////// + +void test_packable_tree::run_test() +{ + FUNCDEF("run_test"); + + const file_info farfle(filename("arf"), 2010); + const file_info empty; + const file_info snood(filename("wookie"), 8888); + + { + // simple creation, packing, unpacking, destruction tests on a blank object. + many_tree gruntcake(farfle); + byte_array packed_form; + int pack_guess = gruntcake.packed_size(); + gruntcake.pack(packed_form); + ASSERT_EQUAL(pack_guess, packed_form.length(), FUNKIT("packed length is incorrect")); + many_tree untbake_target(empty); + ASSERT_TRUE(untbake_target.unpack(packed_form), FUNKIT("unpack operation failed")); + ASSERT_EQUAL(untbake_target.get_info(), gruntcake.get_info(), + FUNKIT("unpack had wrong contents")); + } + + { + // recursive packing tests... + // first layer. + many_tree *spork = new many_tree(farfle); + many_tree *limpet = new many_tree(empty); + many_tree *congo = new many_tree(snood); + many_tree *dworkin = new many_tree(empty); + many_tree *greep = new many_tree(farfle); + // second layer. + many_tree *flep = new many_tree(snood); + many_tree *glug = new many_tree(empty); + many_tree *aptitoot = new many_tree(farfle); + // third layer. + many_tree *grog = new many_tree(snood); + // connect first to second. + flep->attach(spork); + flep->attach(limpet); + glug->attach(congo); + aptitoot->attach(dworkin); + aptitoot->attach(greep); + // connect second to third. + grog->attach(flep); + grog->attach(glug); + grog->attach(aptitoot); + + // now recursively pack that bad boy three level tree. + byte_array packed; + int size_guess = grog->recursive_packed_size(); + grog->recursive_pack(packed); + ASSERT_EQUAL(size_guess, packed.length(), "recursive_packed_size failed"); + tree_defacto factotum; + packable_tree *unpacked = many_tree::recursive_unpack(packed, factotum); + ASSERT_TRUE(unpacked, "recursive_unpack failed"); + ASSERT_TRUE(dynamic_cast(unpacked), "recursive_unpack has wrong type"); + many_tree *survivor = dynamic_cast(unpacked); + +if (survivor) { +} + +//compare trees? + + } + +} + +////////////// + diff --git a/nucleus/library/tests_nodes/test_symbol_tree.cpp b/nucleus/library/tests_nodes/test_symbol_tree.cpp new file mode 100644 index 00000000..ef9917c4 --- /dev/null +++ b/nucleus/library/tests_nodes/test_symbol_tree.cpp @@ -0,0 +1,93 @@ +/*****************************************************************************\ +* * +* Name : test_symbol_tree * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Creates a symbol_tree and performs some operations on it to assure * +* basic functionality. * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include +//#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace nodes; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +#define DEBUG_SYMBOL_TREE + +class test_symbol_tree : public virtual unit_base, virtual public application_shell +{ +public: + test_symbol_tree() {} + DEFINE_CLASS_NAME("test_symbol_tree"); + int execute(); +}; + +int test_symbol_tree::execute() +{ + LOG("please check memory usage and record it, then hit a key to start testing."); + + try { + symbol_tree t("blork"); + symbol_tree *curr = &t; + for (int i = 0; i < 40000; i++) { + // if the current node has any branches, we'll jump on one as the next + // place. + if (curr->branches()) { + // move to a random branch. + int which = randomizer().inclusive(0, curr->branches() - 1); + curr = (symbol_tree *)curr->branch(which); + } + astring rando = string_manipulation::make_random_name(1, 10); + curr->add(new symbol_tree(rando)); + } + LOG("check memory usage now with full size. then hit a key."); + } catch (...) { + LOG("crashed during tree stuffing."); + return 1; + } + + LOG("check memory usage after the run. then hit a key to end " + "the program."); + +//create a tree structure... +//perform known operations and validate shape of tree. + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_symbol_tree, ) + diff --git a/nucleus/library/tests_nodes/test_tree.cpp b/nucleus/library/tests_nodes/test_tree.cpp new file mode 100644 index 00000000..5d3516ce --- /dev/null +++ b/nucleus/library/tests_nodes/test_tree.cpp @@ -0,0 +1,307 @@ +/* +* Name : test_tree * +* Author : Chris Koeritz * +* Purpose: * +* Tests out the tree class. * +** +* Copyright (c) 1993-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace nodes; +using namespace loggers; +using namespace structures; +using namespace unit_test; + +//#define DEBUG_TEST_TREE + // uncomment if you want the noisy streams version. + +const int test_iterations = 20; + +class bogotre : public packable, public tree +{ +public: + bogotre(const char *start = NIL) : who_cares(42), i_sure_dont('l'), + another_useless_int(23) { + astring to_init(start); + if (to_init.length() < 1) to_init += "ack"; + to_init.stuff(the_actual_string, minimum(to_init.length()+1, 500)); + } + DEFINE_CLASS_NAME("bogotre"); + virtual ~bogotre() {} + virtual void pack(byte_array &packed_form) const; + virtual bool unpack(byte_array &to_unpack); + virtual int packed_size() const; + virtual abyte *held() const { return (abyte *)the_actual_string; } + virtual void print() const { +#ifdef DEBUG_TEST_TREE + printf(the_actual_string); +#endif + } + +private: + char the_actual_string[500]; + int who_cares; + char i_sure_dont; + int another_useless_int; +}; + +////////////// + +// forward. +typedef bogotre larch; +typedef void (applier)(larch *apply_to); +typedef tree::iterator traveller; + +class test_tree : public virtual unit_base, virtual public application_shell +{ +public: + test_tree() : application_shell() {} + DEFINE_CLASS_NAME("test_tree"); + virtual int execute(); + static void print_node(larch *curr_node); + static larch *next(larch *&move, larch *hook, traveller &skip); + static void apply(larch *apply_to, applier *to_apply, + tree::traversal_directions order); +}; + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) + +int bogotre::packed_size() const +{ return strlen(the_actual_string) + 1 + sizeof(int) * 2 + sizeof(abyte); } + +void bogotre::pack(byte_array &packed_form) const +{ + FUNCDEF("pack"); + astring(the_actual_string).pack(packed_form); + structures::attach(packed_form, who_cares); + structures::attach(packed_form, i_sure_dont); + structures::attach(packed_form, another_useless_int); +} + +bool bogotre::unpack(byte_array &packed_form) +{ + FUNCDEF("unpack"); + // 5 is the magic knowledge of minimum packed string. +//hmmm: make the minimum packed size a property of packables? + ASSERT_FALSE(packed_form.length() < + int(1 + sizeof(who_cares) + sizeof(i_sure_dont) + sizeof(another_useless_int)), + "size of package should be correct"); + astring unpacked; + ASSERT_TRUE(unpacked.unpack(packed_form), "should be able to retrieve string"); + ASSERT_TRUE(structures::detach(packed_form, who_cares), "should retrieve who_cares"); + ASSERT_TRUE(structures::detach(packed_form, i_sure_dont), "should retrieve i_sure_dont"); + ASSERT_TRUE(structures::detach(packed_form, another_useless_int), + "should retrieve another_..."); + + ASSERT_EQUAL(who_cares, 42, "bogotre_unpack - right value held in first int"); + ASSERT_EQUAL(i_sure_dont, 'l', "bogotre_unpack - right character held"); + ASSERT_EQUAL(another_useless_int, 23, "bogotre_unpack - right value held in second int"); + return true; +} + +////////////// + +/* +bogotre *togen(char *to_store) +{ bogotre *to_return = new bogotre(astring(to_store).s()); return to_return; } +*/ + +void test_tree::print_node(larch *curr_node) +{ + FUNCDEF("print_node"); + ASSERT_TRUE(curr_node, "tree shouldn't be nil"); + bogotre *real_curr = dynamic_cast(curr_node); + ASSERT_TRUE(real_curr, "contents shouldn't be nil"); + astring to_examine((char *)real_curr->held()); +#ifdef DEBUG_TEST_TREE + to_examine += " "; + printf(to_examine.s()); +//remove it again if we reenable the cut. +#endif +// if (to_examine == to_look_for) real_curr->cut(); +} + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*this) + +////////////// + +larch *test_tree::next(larch *&move, larch *formal(hook), traveller &skip) +{ move = dynamic_cast(skip.next()); return move; } + +void test_tree::apply(larch *apply_to, applier *to_apply, + tree::traversal_directions order) +{ + larch *curr = NIL; + for (traveller skippy = apply_to->start(order); + next(curr, apply_to, skippy); ) to_apply(curr); +} + +int test_tree::execute() +{ + FUNCDEF("execute"); + for (int qq = 0; qq < test_iterations; qq++) { + larch *e1 = new larch("a"); + larch *e2 = new larch("b"); + larch *e3 = new larch("+"); + e3->attach(e1); + e3->attach(e2); + + larch *e4 = new larch("c"); + larch *e5 = new larch("-"); + e5->attach(e3); + e5->attach(e4); + + larch *e6 = new larch(">"); + larch *e7 = new larch("23"); + e6->attach(e5); + e6->attach(e7); + + larch *e8 = new larch("d"); + larch *e9 = new larch("="); + e9->attach(e8); + e9->attach(e6); + +#ifdef DEBUG_TEST_TREE + printf("infix is "); +#endif + apply(e9, print_node, tree::infix); +#ifdef DEBUG_TEST_TREE + printf("\nprefix is "); +#endif + apply(e9, print_node, tree::prefix); +#ifdef DEBUG_TEST_TREE + printf("\npostfix is "); +#endif + apply(e9, print_node, tree::postfix); +#ifdef DEBUG_TEST_TREE + printf("\n"); + printf("branches is "); +#endif + apply(e9, print_node, tree::to_branches); +#ifdef DEBUG_TEST_TREE + printf("\n"); + printf("branches reversed is "); +#endif + apply(e9, print_node, tree::reverse_branches); +#ifdef DEBUG_TEST_TREE + printf("\n"); + printf("before first pack"); +#endif + byte_array packed_e9(0); + int sizzle = e9->packed_size(); + e9->pack(packed_e9); + ASSERT_EQUAL(sizzle, packed_e9.length(), "packed size should agree with results"); +#ifdef DEBUG_TEST_TREE + printf("after first pack, size is %d\n", packed_e9.length()); +#endif + larch *new_e9 = new larch(); + new_e9->unpack(packed_e9); +#ifdef DEBUG_TEST_TREE + printf("New tree after unpacking is (infix order):\n"); +#endif + apply(new_e9, print_node, tree::infix); +#ifdef DEBUG_TEST_TREE + printf("\n"); +#endif +/* +#ifdef DEBUG_TEST_TREE + printf("the following dumps are in the order: infix, prefix, postfix.\n\n"); + printf("now trying cut on the character '>':\n"); +#endif + to_look_for = ">"; + new_e9->apply(&print_node, tree::infix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + new_e9->apply(&print_node, tree::prefix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + new_e9->apply(&print_node, tree::postfix); +#ifdef DEBUG_TEST_TREE + p("\nnow trying cut on the character +:\n"); +#endif + to_look_for = "+"; + new_e9->apply(&print_node, tree::infix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + new_e9->apply(&print_node, tree::prefix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + new_e9->apply(&print_node, tree::postfix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + to_look_for = ""; + +#ifdef DEBUG_TEST_TREE + p("okay, trying to resume at -\n"); +#endif + e5->resume(&print_node, tree::infix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + e5->resume(&print_node, tree::prefix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif + e5->resume(&print_node, tree::postfix); +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif +*/ +#ifdef DEBUG_TEST_TREE + printf("deleting\n"); +#endif + delete e9; +/* +printf("second pack\n"); + byte_array second_pack; +printf("packing\n"); + new_e9->pack(second_pack); +#ifdef DEBUG_TEST_TREE + printf("after second pack, size is %d\n", size); +#endif +*/ + delete new_e9; +/* + larch *newest_e9 = new larch(SELF_CLEANING); + newest_e9->unpack(second_pack); +#ifdef DEBUG_TEST_TREE + printf("after second unpack... tree is (infix):\n"); +#endif + newest_e9->apply(print_node, tree::infix); + delete newest_e9; +#ifdef DEBUG_TEST_TREE + p("\n"); +#endif +*/ + } + return final_report(); +} + +HOOPLE_MAIN(test_tree, ) + diff --git a/nucleus/library/tests_structures/bogon.cpp b/nucleus/library/tests_structures/bogon.cpp new file mode 100644 index 00000000..4bd88123 --- /dev/null +++ b/nucleus/library/tests_structures/bogon.cpp @@ -0,0 +1,53 @@ +/*****************************************************************************\ +* * +* Name : bogon * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* A simple test object for amorphs. * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "bogon.h" + +#include + +using namespace basis; +using namespace structures; + +bogon::bogon(abyte *to_copy) : my_held(NIL) +{ + if (to_copy) { + astring t((char *)to_copy); + if (t.length()) { + my_held = new abyte[t.length() + 1]; + t.stuff((char *)my_held, t.length() + 1); + } + } +} + +bogon::bogon(const bogon &to_copy) : my_held(NIL) { operator = (to_copy); } + +bogon &bogon::operator = (const bogon &to_copy) { + if (this == &to_copy) return *this; + astring t((char *)to_copy.my_held); + if (my_held) delete [] my_held; + my_held = new abyte[t.length() + 1]; + t.stuff((char *)my_held, t.length() + 1); + return *this; +} + +bogon::~bogon() { if (my_held) delete [] my_held; } + +abyte *bogon::held() const { return my_held; } + +int bogon::size() const { return my_held? int(strlen((char *)my_held) + 1) : 0; } + diff --git a/nucleus/library/tests_structures/bogon.h b/nucleus/library/tests_structures/bogon.h new file mode 100644 index 00000000..77edfa59 --- /dev/null +++ b/nucleus/library/tests_structures/bogon.h @@ -0,0 +1,49 @@ +#ifndef BOGON_CLASS +#define BOGON_CLASS + +/*****************************************************************************\ +* * +* Name : bogon * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* A simple test object for amorphs. * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#define DEBUG_ARRAY +#define DEBUG_AMORPH + +#include +#include +#include + +class bogon +{ +public: + bogon(basis::abyte *to_copy); + + bogon(const bogon &to_copy); + + bogon &operator = (const bogon &to_copy); + + ~bogon(); + + basis::abyte *held() const; + + int size() const; + +private: + basis::abyte *my_held; +}; + +#endif + diff --git a/nucleus/library/tests_structures/makefile b/nucleus/library/tests_structures/makefile new file mode 100644 index 00000000..eb038004 --- /dev/null +++ b/nucleus/library/tests_structures/makefile @@ -0,0 +1,14 @@ +include cpp/variables.def + +PROJECT = tests_structures +TYPE = test +SOURCE = bogon.cpp +TARGETS = test_amorph.exe test_hash_table.exe test_int_hash.exe test_matrix.exe \ + test_memory_limiter.exe test_packing.exe test_stack.exe test_unique_id.exe \ + test_bit_vector.exe test_set.exe test_string_table.exe test_symbol_table.exe \ + test_version.exe +LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ + structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def diff --git a/nucleus/library/tests_structures/test_amorph.cpp b/nucleus/library/tests_structures/test_amorph.cpp new file mode 100644 index 00000000..e5dfb1ca --- /dev/null +++ b/nucleus/library/tests_structures/test_amorph.cpp @@ -0,0 +1,536 @@ +/* +* Name : test_byte_array_amorph +* Author : Chris Koeritz +* Purpose: +* Puts the amorph object through its paces. +** +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "bogon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define DEBUG_ARRAY + // uncomment to enable array debugging. + +#define DEBUG_AMORPH + // uncomment to enable amorph debugging. + +//#define DEBUG_TEST_AMORPH + // uncomment for this program to be noisier. + +#ifdef DEBUG_TEST_AMORPH + #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) +#else + #define LOG(to_print) {} +#endif + +////////////// + +class t_amorph : virtual public unit_base, virtual public application_shell +{ +public: + t_amorph() : unit_base() {} + DEFINE_CLASS_NAME("t_amorph"); + int test_bogon_amorph(); + int test_byte_array_amorph(); + byte_array fake_pack(amorph &me); + int compare(amorph &one, amorph &two); + amorph *fake_amorph_unpack(byte_array &packed_amorph); + int compare(const amorph &one, const amorph &two); + + struct blob_hold { int size; int offset; }; + + virtual int execute(); +}; + +#define PACK_BLOB_SIZE(max_limbs) (max_limbs * sizeof(blob_hold)) + +HOOPLE_MAIN(t_amorph, ); + +////////////// + +const int default_test_iterations = 2; + +const int MAX_LIMBS = 200; + // the highest number of items stored in the amorphs here. + +const int MIN_CHUBBY = 60; + // the smallest chunk to allocate for storing text strings... all strings + // must therefore be shorter than this length. +const int MAX_RANDO = 275; + // the maximum amount of space to add when allocating a randomly sized chunk. + +#define PROGRAM_NAME astring("test_amorph") + +int t_amorph::compare(amorph &one, amorph &two) +{ + FUNCDEF("compare amorph"); + ASSERT_EQUAL(one.elements(), two.elements(), "elements comparison"); + if (one.elements() != two.elements()) return false; + ASSERT_EQUAL(one.valid_fields(), two.valid_fields(), "valid fields comparison"); + if (one.valid_fields() != two.valid_fields()) return false; + for (int i = 0; i < one.elements(); i++) { + if (!one.get(i) && !two.get(i)) continue; + ASSERT_FALSE(!one.get(i) || !two.get(i), "inequal emptiness"); + ASSERT_EQUAL(one.get(i)->length(), two.get(i)->length(), "inequal sizes"); + if (one.get(i)->length() > 0) { + ASSERT_INEQUAL(one[i]->observe(), two[i]->observe(), "pointer in use twice"); + ASSERT_FALSE(memcmp(one[i]->observe(), two[i]->observe(), one[i]->length()), + "inequal contents"); + } + } + return true; +} + +byte_array t_amorph::fake_pack(amorph &me) +{ + FUNCDEF("fake_pack"); + // snagged from the packable_amorph pack function! + // count the whole size needed to store the amorph. + int amo_size = 0; + amorph hold_packed_bits(me.elements()); + + for (int i = 0; i < me.elements(); i++) + if (me.get(i) && me.get(i)->length()) { + byte_array packed_item; + attach(packed_item, *me[i]); + byte_array *to_stuff = new byte_array(packed_item); + hold_packed_bits.put(i, to_stuff); + amo_size += packed_item.length(); + } + int len = amo_size + sizeof(int) + PACK_BLOB_SIZE(me.elements()); + + // allocate a storage area for the packed form. + byte_array to_return(len); + int temp = me.elements(); + memcpy((int *)to_return.access(), &temp, sizeof(int)); + // size of package is stored at the beginning of the memory. + + int current_offset = sizeof(int); + // the indices into the packed form are located after the amorph header. + blob_hold *blob_array = (blob_hold *)(to_return.access() + current_offset); + current_offset += PACK_BLOB_SIZE(me.elements()); + + // the entire amorph is replicated into the new buffer. + for (int j = 0; j < me.elements(); j++) { + // the offset of this limb in the packed area is saved in the hold. + blob_array[j].size + = (hold_packed_bits[j]? hold_packed_bits[j]->length() : 0); + blob_array[j].offset = current_offset; + if (hold_packed_bits[j] && hold_packed_bits[j]->length()) { + // the actual data is copied.... + memcpy(to_return.access() + current_offset, + (abyte *)hold_packed_bits[j]->observe(), + hold_packed_bits[j]->length()); + // and the "address" is updated. + current_offset += hold_packed_bits[j]->length(); + } + } + ASSERT_EQUAL(current_offset, len, "offset is incorrect after packing"); + return to_return; +} + +amorph *t_amorph::fake_amorph_unpack(byte_array &packed_amorph) +{ + // snagged from the packable_amorph unpack function! + int max_limbs; + memcpy(&max_limbs, (int *)packed_amorph.access(), sizeof(max_limbs)); + amorph *to_return = new amorph(max_limbs); + + blob_hold *blob_array = new blob_hold[max_limbs]; + memcpy(blob_array, (blob_hold *)(packed_amorph.access() + + sizeof(int)), PACK_BLOB_SIZE(max_limbs)); + for (int i = 0; i < to_return->elements(); i++) + if (blob_array[i].size) { + abyte *source = packed_amorph.access() + blob_array[i].offset; + byte_array packed_byte_array(blob_array[i].size, source); + byte_array *unpacked = new byte_array; + detach(packed_byte_array, *unpacked); + to_return->put(i, unpacked); + } + delete [] blob_array; + return to_return; +} + +int t_amorph::test_byte_array_amorph() +{ + FUNCDEF("test_byte_array_amorph"); + LOG("start of amorph of abyte array test"); + for (int qq = 0; qq < default_test_iterations; qq++) { + LOG(astring(astring::SPRINTF, "index %d", qq)); + { + // some simple creation and stuffing tests.... + amorph fred(20); + amorph gen(10); + for (int i=0; i < 10; i++) { + byte_array *gens = new byte_array(8, (abyte *)"goodbye"); + gen.put(i, gens); + } + for (int j = 0; j < 20; j++) { + byte_array *freds = new byte_array(6, (abyte *)"hello"); + fred.put(j, freds); + } + amorph_assign(gen, fred); + LOG("done with fred & gen"); + } + + LOG("before fred creation"); + chaos randomizer; + amorph fred(MAX_LIMBS - 1); + fred.append(NIL); // add one to make it max limbs big. + LOG("after append nil"); + { + for (int i = 0; i < fred.elements(); i++) { + int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); + astring text("bogus burfonium nuggets"); + astring burph(astring::SPRINTF, " ung %d ", i); + text += burph; + abyte *temp = new abyte[size]; + text.stuff((char *)temp, text.length()+1); + byte_array *to_stuff = new byte_array(size, temp); + fred.put(i, to_stuff); + delete [] temp; + } + } + LOG("after first loop"); + { + amorph bungee3; + amorph_assign(bungee3, fred); + amorph burglar2; + amorph_assign(burglar2, bungee3); + amorph trunklid; + amorph_assign(trunklid, burglar2); + ASSERT_INEQUAL(trunklid.elements(), 0, "const constructor test - no elements!"); + } + LOG("after copies performed"); + { + astring text; + text = "hello this is part one."; + LOG(astring(astring::SPRINTF, "len is %d, content is %s", + text.length(), text.observe())); + char *tadr = text.access(); + abyte *badr = (abyte *)tadr; + byte_array *to_stuff = new byte_array(text.length() + 1, badr); + fred.put(183, to_stuff); + text = "wonky tuniea bellowbop"; + byte_array *to_stuff1 = new byte_array(text.length()+1, (abyte *)text.s()); + fred.put(90, to_stuff1); + + text = "frunkwioioio"; + byte_array *to_stuff2 = new byte_array(text.length()+1, (abyte *)text.s()); + fred.put(12, to_stuff2); + + fred.clear(98); fred.clear(122); fred.clear(123); + fred.clear(256); + fred.clear(129); + fred.zap(82, 90); + fred.zap(93, 107); + } + LOG("after second loop"); + { + byte_array packed = fake_pack(fred); + LOG(astring(astring::SPRINTF, "done packing in %s, pack has %d " + "elems.", class_name(), packed.length())); + amorph *new_fred = fake_amorph_unpack(packed); + LOG("done unpacking in test_amorph"); + ASSERT_TRUE(compare(fred, *new_fred), "first pack test, amorphs not the same"); + abyte *cont1 + = (new_fred->get(14)? (*new_fred)[14]->access() : (abyte *)"NIL"); + abyte *cont2 + = (new_fred->get(20)? (*new_fred)[20]->access() : (abyte *)"NIL"); + abyte *cont3 + = (new_fred->get(36)? (*new_fred)[36]->access() : (abyte *)"NIL"); + + if (cont1) LOG(astring(astring::SPRINTF, "14: %s", cont1)); + if (cont2) LOG(astring(astring::SPRINTF, "20: %s", cont2)); + if (cont3) LOG(astring(astring::SPRINTF, "36: %s", cont3)); + LOG("fields all compare identically after pack and unpack"); + byte_array packed_second = fake_pack(*new_fred); + delete new_fred; + amorph *newer_fred = fake_amorph_unpack(packed_second); + ASSERT_TRUE(compare(*newer_fred, fred), "second pack test, amorphs not the same"); + delete newer_fred; + } + + { + amorph fred(randomizer.inclusive(20, 30)); + int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); + astring text("bogus burfonium nuggets"); + astring burph(astring::SPRINTF, " ung %d ", 2314); + text += burph; + byte_array intermed(size); + + for (int i = 0; i < fred.elements(); i += 5) { + byte_array *to_stuff = new byte_array(size, intermed.access()); + memcpy(intermed.access(), (abyte *)text.s(), text.length() + 1); + fred.put(i, to_stuff); + } + fred.clear_all(); + for (int j = 0; j < fred.elements(); j += 5) { + byte_array *to_stuff = new byte_array(size, intermed.access()); + memcpy(intermed.access(), (abyte *)text.s(), text.length() + 1); + fred.put(j, to_stuff); + } + text = "frunkwioioio"; + byte_array *to_stuff = new byte_array(text.length()+1, (abyte *)text.s()); + fred.put(12, to_stuff); + fred.clear_all(); + } + LOG("survived the clear_alls"); + { + amorph *ted = new amorph(0); + amorph_assign(*ted, fred); + ASSERT_TRUE(compare(*ted, fred), "ted and fred aren't the same"); + { + amorph *george = new amorph(0); + amorph_assign(*george, fred); + ASSERT_TRUE(compare(*george, fred), "fred and george aren't the same"); + ted->zap(3, 20); + george->zap(3, 10); + george->zap(3, 12); + ASSERT_TRUE(compare(*ted, *george), "after zap, ted and george aren't the same"); + ted->adjust(ted->elements() - 20); + george->adjust(george->elements() - 5); + george->adjust(george->elements() - 5); + george->adjust(george->elements() - 5); + george->adjust(george->elements() - 5); + ASSERT_TRUE(compare(*ted, *george), "after adjust, ted and george aren't the same"); + delete george; + } + delete ted; + } + } + return 0; +} + +int t_amorph::compare(const amorph &one, const amorph &two) +{ + FUNCDEF("compare amorph"); + if (one.elements() != two.elements()) return false; + for (int i = 0; i < one.elements(); i++) { + if (!one.get(i) && !two.get(i)) continue; + ASSERT_FALSE(!one.get(i) || !two.get(i), "both should be non-nil"); + ASSERT_EQUAL(one.get(i)->size(), two.get(i)->size(), "sizes should be equal"); + if (one.get(i)->size() > 0) { + ASSERT_INEQUAL(one.get(i)->held(), two.get(i)->held(), "pointer should not be in use twice"); + ASSERT_FALSE(memcmp(one.get(i)->held(), two.get(i)->held(), one.get(i)->size()), + "contents should be equal"); + } + } + return true; +} + +int t_amorph::test_bogon_amorph() +{ + FUNCDEF("test_bogon_amorph"); + LOG("start of amorph of bogon test"); + for (int qq = 0; qq < default_test_iterations; qq++) { + LOG(astring(astring::SPRINTF, "index %d", qq)); + { + // some simple creation and stuffing tests.... + amorph fred(20); + amorph gen(10); + for (int i = 0; i < 10; i++) { + bogon *gens = new bogon((abyte *)"goodbye"); + gen.put(i, gens); + } + for (int j = 0; j < 20; j++) { + bogon *freds = new bogon((abyte *)"hello"); + fred.put(j, freds); + } + ASSERT_FALSE(compare(fred, gen), "fred and gen ARE the same"); + amorph_assign(gen, fred); + ASSERT_TRUE(compare(fred, gen), "fred and gen aren't the same"); + } + + chaos randomizer; + + amorph fred(MAX_LIMBS); + + LOG("after append nil"); + { + for (int i = 0; i < fred.elements(); i++) { + int size = MIN_CHUBBY + randomizer.inclusive(0, MAX_RANDO); + astring text("bogus burfonium nuggets"); + astring burph(astring::SPRINTF, " ung %d ", i); + text += burph; + abyte *temp = new abyte[size]; + text.stuff((char *)temp, text.length()+1); + bogon *to_stuff = new bogon(temp); + fred.put(i, to_stuff); + delete [] temp; + } + } + + LOG("after first loop"); + { + amorph bungee3; + amorph_assign(bungee3, fred); + amorph burglar2; + amorph_assign(burglar2, bungee3); + amorph_assign(burglar2, bungee3); + amorph trunklid; + amorph_assign(trunklid, burglar2); + ASSERT_TRUE(trunklid.elements(), "const constructor test: no elements!"); + } + { + astring text; + text = "hello this is part one."; + bogon *to_stuff = new bogon((abyte *)text.s()); + fred.put(32, to_stuff); + + text = "wonky tuniea bellowbop"; + bogon *to_stuff1 = new bogon((abyte *)text.s()); + fred.put(84, to_stuff1); + + text = "frunkwioioio"; + bogon *to_stuff2 = new bogon((abyte *)text.s()); + fred.put(27, to_stuff2); + + fred.clear(98); fred.clear(122); fred.clear(123); + fred.clear(256); + fred.clear(129); + fred.zap(82, 90); + fred.zap(93, 107); + } + LOG("after second loop"); + { + amorph fred(randomizer.inclusive(20, 30)); + astring text("bogus burfonium nuggets"); + astring burph(astring::SPRINTF, " ung %d ", 2314); + text += burph; + + for (int i = 0; i < fred.elements(); i += 5) { + bogon *to_stuff = new bogon((abyte *)text.s()); + fred.put(i, to_stuff); + } + fred.clear_all(); + for (int j = 0; j < fred.elements(); j += 5) { + bogon *to_stuff = new bogon((abyte *)text.s()); + fred.put(j, to_stuff); + } + text = "frunkwioioio"; + bogon *to_stuff = new bogon((abyte *)text.s()); + fred.put(6, to_stuff); + fred.clear_all(); + } + LOG("survived the clear_alls"); + { + amorph *ted = new amorph(); + amorph_assign(*ted, fred); + ASSERT_TRUE(compare(*ted, fred), "after assign, ted and fred aren't the same"); + { + amorph *george = new amorph(); + amorph_assign(*george, fred); + ASSERT_TRUE(compare(*george, fred), "pre-zap, george and fred aren't the same"); + ted->zap(3, 20); + george->zap(3, 10); + george->zap(3, 12); + ASSERT_TRUE(compare(*ted, *george), "after zap, ted and george aren't the same"); + ted->adjust(ted->elements()-20); + george->adjust(george->elements()-5); + george->adjust(george->elements()-5); + george->adjust(george->elements()-5); + george->adjust(george->elements()-5); + ASSERT_TRUE(compare(*ted, *george), "after more zaps, ted and george aren't the same"); + delete george; + } + delete ted; + } + } + return 0; +} + +const int MAX_TEST_DURATION = 1 * MINUTE_ms; + // each of the tests calling on the templated tester will take this long. + +const int MAX_SIMULTANEOUS_OBJECTS = 42; // the maximum length tested. + +//hmmm: this test_amorph_of is not completed. + +template +int test_amorph_of(const contents &bogus) +{ + chaos rando; + + // these are the actions we try on the amorph during the test. + // the first and last elements must be identical to the first and last + // tests to perform. + enum actions { first, do_zap = first, do_adjust, do_assign, + + + do_borrow, last = do_borrow}; + + time_stamp exit_time(::MAX_TEST_DURATION); + while (time_stamp() < exit_time) { + int index = rando.inclusive(0, ::MAX_SIMULTANEOUS_OBJECTS - 1); + int choice = rando.inclusive(first, last); + switch (choice) { + case do_zap: { + + break; + } + case do_adjust: { + + break; + } + case do_assign: { + + break; + } + case do_borrow: { + + break; + } + } + } +} + +int t_amorph::execute() +{ + SETUP_COMBO_LOGGER; + int errs = 0; + int retval = test_byte_array_amorph(); + if (retval != 0) errs += retval; + retval = test_bogon_amorph(); + if (retval != 0) errs += retval; + +//incorporate these errors somehow also. + +// if (retval == 0) +// critical_events::alert_message("amorph:: works for those functions tested."); +// else +// critical_events::alert_message("amorph:: there were errors!"); + return final_report(); +} + diff --git a/nucleus/library/tests_structures/test_bit_vector.cpp b/nucleus/library/tests_structures/test_bit_vector.cpp new file mode 100644 index 00000000..068f8a81 --- /dev/null +++ b/nucleus/library/tests_structures/test_bit_vector.cpp @@ -0,0 +1,163 @@ +/*****************************************************************************\ +* * +* Name : test_bit_vector * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) + +#define MAX_TEST 100 +#define FOOP_MAX 213 + +////////////// + +class test_bit_vector : virtual public unit_base, virtual public application_shell +{ +public: + test_bit_vector() : unit_base() {} + DEFINE_CLASS_NAME("test_bit_vector"); + virtual int execute(); +}; + +HOOPLE_MAIN(test_bit_vector, ); + +////////////// + +struct test_struct { basis::un_int store; int posn; int size; }; + +int test_bit_vector::execute() +{ + FUNCDEF("execute"); + SETUP_COMBO_LOGGER; + + const array unused; + + chaos randomizer; + bit_vector foop(FOOP_MAX); + + for (int i = 0; i < MAX_TEST; i++) { + // sets a random bit and finds that one. + int rando = randomizer.inclusive(0, FOOP_MAX-1); + foop.light(rando); + int found = foop.find_first(true); + ASSERT_EQUAL(found, rando, "find first locates first true"); + foop.clear(rando); + + foop.resize(FOOP_MAX); + ASSERT_EQUAL(0, foop.find_first(0), "locating location of first zero"); + ASSERT_EQUAL(common::NOT_FOUND, foop.find_first(1), "showing there are no one bits"); + for (int i = 0; i < 12; i++) foop.light(i); + ASSERT_EQUAL(12, foop.find_first(0), "finding first on partially set vector"); + + foop.light(FOOP_MAX); // shouldn't work, but shouldn't die. + ASSERT_FALSE(foop.on(FOOP_MAX), "bit_on should not be lit past end of vector"); + + // sets a bunch of random bits. + for (int j = 0; j < 40; j++) { + int rando = randomizer.inclusive(0, FOOP_MAX-1); + foop.light(rando); + } + bit_vector foop2(FOOP_MAX, ((const byte_array &)foop).observe()); + ASSERT_EQUAL(foop, foop2, "after lighting, vectors should be identical"); + + { + // this block tests the subvector and int storage/retrieval routines. + if (foop.bits() < 90) foop.resize(90); // make sure we have room to play. + + array tests; + test_struct t1 = { 27, 15, 5 }; + tests += t1; + test_struct t2 = { 8, 25, 4 }; + tests += t2; + test_struct t3 = { 1485, 34, 16 }; + tests += t3; + test_struct t4 = { 872465, 50, 32 }; + tests += t4; + + for (int i = 0; i < tests.length(); i++) { + ASSERT_TRUE(foop.set(tests[i].posn, tests[i].size, tests[i].store), + "storing int in vector should work"); + +//hmmm: make this a test case! +// bit_vector found = foop.subvector(tests[i].posn, tests[i].posn+tests[i].size-1); +// LOG(astring(astring::SPRINTF, "contents found:\n%s", found.text_form().s())); + + basis::un_int to_check = foop.get(tests[i].posn, tests[i].size); + if (to_check != tests[i].store) + LOG(a_sprintf("int found at %d in vector (%u) is different than what was stored (%u).", + i, to_check, tests[i].store)); + ASSERT_EQUAL((int)to_check, (int)tests[i].store, "should see expected int stored in vector"); + } + } + + { + // tests random resizings and resettings. + int number_of_loops = randomizer.inclusive(50, 150); + for (int i = 0; i < number_of_loops; i++) { + int which_to_do = randomizer.inclusive(1, 3); + switch (which_to_do) { + case 1: { + // resize. + int new_size = randomizer.inclusive(0, 32000); + foop.resize(new_size); + break; + } + case 2: { + // reset. + int new_size = randomizer.inclusive(0, 32000); + foop.reset(new_size); + break; + } + case 3: { + // random sets. + int sets_to_do = randomizer.inclusive(40, 280); + for (int i = 0; i < sets_to_do; i++) { + int rando = randomizer.inclusive(0, foop.bits()); + if (randomizer.inclusive(0, 1)) foop.light(rando); + else foop.clear(rando); + } + break; + } + } + } + } + + foop.reset(FOOP_MAX); // to clear before next loop. + } + + return final_report(); +} + diff --git a/nucleus/library/tests_structures/test_hash_table.cpp b/nucleus/library/tests_structures/test_hash_table.cpp new file mode 100644 index 00000000..0e2e214c --- /dev/null +++ b/nucleus/library/tests_structures/test_hash_table.cpp @@ -0,0 +1,540 @@ +/* +* Name : test_hash_table +* Author : Chris Koeritz +* Purpose: +* Tests out the hash_table abstract data type. +** +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +///using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_HASH_TABLE + // uncomment for noisier run. + +const double TEST_DURATION = 0.014 * MINUTE_ms; +//const double TEST_DURATION = 20 * SECOND_ms; + +const int MAX_ELEMENTS = 8; + // we start low since we will rehash occasionally. + +////////////// + +enum test_actions { + FIRST_TEST = 38, // place-holder. + ADD = FIRST_TEST, + // adds an item that is probably new. + ADD_ADD, + // adds an item that is probably new, followed by another item under the + // same key id. this ensures the overwriting gets tested. + ZAP, + // finds an item we know is in the list and whacks it. + ADD_ZAP, + // adds a new item and immediately finds and zaps it. + ZAP_ADD, + // zaps an item that we know about and then adds a new item with the same + // identifier. + FIND, + // locates an item in the list which we know should exist. + ACQUIRE, + // grabs an item out of the list (and tosses it). + FIND_ZAP_ADD, + // finds an item we know should exist, zaps it out of the list, then adds + // a new item with the same id. + ACQUIRE_ADD_ZAP, + // removes an item from the list that we know should be there, adds it back + // in, and then whacks it. + FIND_ADD_FIND, + // find an item with a particular id (one that we know should be in the + // list) and then adds a different item using the same id. the new item + // is then sought. + RESET, + // tosses all data out of the hash table. not done very often. + CHECK_SANITY, + // look for any problems or irregularities; print the contents of the list + // if any are found. + REHASH, + // resizes the hash table. + COPY, + // copies a hash table to another hash table. + LAST_TEST = COPY // place-holder; must equal test just prior. +}; + +////////////// + +// a simple object that is used as the contents of the hash_table. + +class data_shuttle +{ +public: + int food_bar; + astring snacky_string; + bool hungry; + byte_array chunk; + chaos chao; + + data_shuttle() + : snacky_string(string_manipulation::make_random_name()), + chunk(chao.inclusive(100, 10000)) {} +}; + +////////////// + +class test_hash_table : virtual public unit_base, virtual public application_shell +{ +public: + test_hash_table(); + + DEFINE_CLASS_NAME("test_hash_table"); + + int raw_random_id(); //!< returns an unvetted random number. + int unused_random_id(); //!< returns an unused (so far) random number. + + int execute(); + // the main startup for the test. + + bool perform_a_test(test_actions test_type); + // carries out the specifics of the "test_type". + + bool pick_a_test(); + // randomly picks one of the test types and performs it. + + static const char *test_name(test_actions test_type); + + // these functions each perform one type of test, which their names indicate. + bool test_add(); + bool test_add_add(); + bool test_zap(); + bool test_add_zap(); + bool test_zap_add(); + bool test_find(); + bool test_acquire(); + bool test_find_zap_add(); + bool test_acquire_add_zap(); + bool test_find_add_find(); + bool test_reset(bool always_run = false); + bool test_check_sanity(); + bool test_copy(); + bool test_rehash(); + + static bool equivalence_applier(const int &key, data_shuttle &item, void *dlink); + +private: + int_set _keys_in_use; // keys that we think are stored in the table. + hash_table _the_table; // our table under test. + int _hits[LAST_TEST - FIRST_TEST + 1]; // tracks our testing activities. + int _tested; // simple counter of number of test calls. +}; + +////////////// + +typedef hash_table our_hash; // cleans up somewhat. + +////////////// + +test_hash_table::test_hash_table() +: application_shell(), + _the_table(rotating_byte_hasher(), MAX_ELEMENTS), + _tested(0) +{ + for (int i = FIRST_TEST; i <= LAST_TEST; i++) + _hits[i - FIRST_TEST] = 0; +} + +int test_hash_table::raw_random_id() +{ + return randomizer().inclusive(-MAXINT32 / 4, MAXINT32 / 4); +} + +int test_hash_table::unused_random_id() +{ + while (true) { + int checking = raw_random_id(); + if (!_keys_in_use.member(checking)) return checking; // got one. + } // keep going until we find unused id. +} + +int test_hash_table::execute() +{ + time_stamp exit_time((int)TEST_DURATION); + while (time_stamp() < exit_time) { + pick_a_test(); + } + test_reset(true); // force it to run at least once. + +#ifdef DEBUG_HASH_TABLE + log(a_sprintf("did %d tests.\n", _tested)); + log(astring("Test Activity:")); + for (int i = 0; i < LAST_TEST - FIRST_TEST + 1; i++) + log(astring(astring::SPRINTF, "%d (%s): %d hits", i + FIRST_TEST, + test_name(test_actions(i + FIRST_TEST)), _hits[i])); + log(a_sprintf("note that test %d will seldom be executed.", RESET)); +#endif + return final_report(); +} + +const char *test_hash_table::test_name(test_actions test_type) +{ + switch (test_type) { + case ADD: return "ADD"; + case ADD_ADD: return "ADD_ADD"; + case ZAP: return "ZAP"; + case ADD_ZAP: return "ADD_ZAP"; + case ZAP_ADD: return "ZAP_ADD"; + case FIND: return "FIND"; + case ACQUIRE: return "ACQUIRE"; + case FIND_ZAP_ADD: return "FIND_ZAP_ADD"; + case ACQUIRE_ADD_ZAP: return "ACQUIRE_ADD_ZAP"; + case FIND_ADD_FIND: return "FIND_ADD_FIND"; + case RESET: return "RESET"; + case COPY: return "COPY"; + case REHASH: return "REHASH"; + case CHECK_SANITY: return "CHECK_SANITY"; + default: return "UnknownTest"; + } +} + +bool test_hash_table::perform_a_test(test_actions test_type) +{ + FUNCDEF("perform_a_test"); + +// log(astring(test_name(test_type)) + " "); + + switch (test_type) { + case ADD: return test_add(); + case ADD_ADD: return test_add_add(); + case ZAP: return test_zap(); + case ADD_ZAP: return test_add_zap(); + case ZAP_ADD: return test_zap_add(); + case FIND: return test_find(); + case ACQUIRE: return test_acquire(); + case FIND_ZAP_ADD: return test_find_zap_add(); + case ACQUIRE_ADD_ZAP: return test_acquire_add_zap(); + case FIND_ADD_FIND: return test_find_add_find(); + case RESET: return test_reset(); + case COPY: return test_copy(); + case REHASH: return test_rehash(); + case CHECK_SANITY: return test_check_sanity(); + default: + ASSERT_TRUE(false, "should not see any missing cases"); + return false; // never gets here. + } +} + +bool test_hash_table::pick_a_test() +{ + _tested++; + return perform_a_test(test_actions(randomizer().inclusive(FIRST_TEST, + LAST_TEST))); +} + +bool test_hash_table::test_add() +{ + FUNCDEF("test_add"); + _hits[ADD - FIRST_TEST]++; + int random_id = raw_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + outcome expected = common::IS_NEW; + if (_keys_in_use.member(random_id)) common::EXISTING; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), expected.value(), + "add should give proper outcome based on expectation"); + if (_keys_in_use.member(random_id)) + return true; // already was there so we replaced. + _keys_in_use.add(random_id); + return true; +} + +////////////// + +hash_table *_hang_on = NIL; + // must be set before calling the apply method. + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) + +bool test_hash_table::equivalence_applier(const int &key, data_shuttle &item, void *dlink) +{ + FUNCDEF("equivalence_applier"); + ASSERT_NON_NULL(dlink, "should have been given name"); + if (!dlink) return false; // fail. + astring test_name = (char *)dlink; + +//application_shell::single_instance()->log(astring("after name check")); + + data_shuttle *found = _hang_on->find(key); + ASSERT_NON_NULL(found, test_name + ": should find equivalent entry in second list"); + if (!found) return false; // bail or we'll crash. + +//application_shell::single_instance()->log(astring("after finding")); + + ASSERT_EQUAL(item.food_bar, found->food_bar, test_name + ": food_bar should not differ"); + ASSERT_EQUAL(item.snacky_string, found->snacky_string, test_name + ": snacky_string should not differ"); + ASSERT_EQUAL(item.hungry, found->hungry, test_name + ": hungry should not differ"); + return true; +} + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*this) + +////////////// + +bool test_hash_table::test_rehash() +{ + FUNCDEF("test_rehash"); + _hang_on = &_the_table; // must happen first. + + // we don't want to rehash too often; it is expensive. + int maybe = randomizer().inclusive(1, 50); + if (maybe < 32) return true; // not this time. + + _hits[REHASH - FIRST_TEST]++; + + hash_table table_copy(rotating_byte_hasher(), + _the_table.estimated_elements()); + +//log("copying table..."); + copy_hash_table(table_copy, _the_table); + // make a copy of the table. + +//log("rehashing table..."); + _the_table.rehash(randomizer().inclusive(1, 20)); +//hmmm: need test of non-existent dehash function that reduces max_bits. + +//log("comparing table..."); + table_copy.apply(equivalence_applier, (void*)func); +//log("done copy and compare."); + + return true; +} + +bool test_hash_table::test_copy() +{ + FUNCDEF("test_copy"); + _hang_on = &_the_table; // must happen first. + + // we don't want to copy too often. it's a heavy operation. + int maybe = randomizer().inclusive(1, 50); + if (maybe > 16) return true; // not this time. + + _hits[COPY - FIRST_TEST]++; + + hash_table table_copy(rotating_byte_hasher(), MAX_ELEMENTS); + +//log("copying table..."); + copy_hash_table(table_copy, _the_table); + // make a copy of the table. + +//log("comparing table..."); + table_copy.apply(equivalence_applier, (void*)func); +//log("done copy and compare."); + + return true; +} + +////////////// + +bool test_hash_table::test_add_add() +{ + FUNCDEF("test_add_add"); + _hits[ADD_ADD - FIRST_TEST]++; + int random_id = unused_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, + "new addition should be seen as such"); + // add the new key if it's really new. + _keys_in_use.add(random_id); + + // second add on same id. + data_shuttle *next_add = new data_shuttle; + next_add->snacky_string = string_manipulation::make_random_name(); + next_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, next_add).value(), our_hash::EXISTING, + "second add should not say first failed"); + + return true; +} + +bool test_hash_table::test_zap() +{ + FUNCDEF("test_zap"); + int maybe = randomizer().inclusive(1, 1000); + if (maybe > 50) return true; + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ZAP - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int dead_key = _keys_in_use[rand_indy]; + _keys_in_use.remove(dead_key); // remove the record of that key. + ASSERT_TRUE(_the_table.zap(dead_key), "key should be present in table"); + return true; +} + +bool test_hash_table::test_add_zap() +{ + FUNCDEF("test_add_zap"); + // add. + _hits[ADD_ZAP - FIRST_TEST]++; + int random_id = unused_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, + "putting new item in should be seen as new"); + // zap. + ASSERT_TRUE(_the_table.zap(random_id), "key should be present after add"); + return true; +} + +bool test_hash_table::test_zap_add() +{ + FUNCDEF("test_zap_add"); + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ZAP_ADD - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + // in the end, the key list state won't be changed unless the test fails. + int dead_key = _keys_in_use[rand_indy]; + ASSERT_TRUE(_the_table.zap(dead_key), "key should be there when we look"); + + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = dead_key; + outcome ret = _the_table.add(dead_key, to_add); + ASSERT_EQUAL(ret.value(), our_hash::IS_NEW, "key should not be present already"); + return true; +} + +bool test_hash_table::test_find() +{ + FUNCDEF("test_find"); + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[FIND - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int find_key = _keys_in_use[rand_indy]; + data_shuttle *found = NIL; + ASSERT_TRUE(_the_table.find(find_key, found), "key should be there as expected"); + ASSERT_NON_NULL(found, "contents should not be NIL"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should have length"); + return true; +} + +bool test_hash_table::test_acquire() +{ + FUNCDEF("test_acquire"); + int maybe = randomizer().inclusive(1, 1000); + if (maybe > 150) return true; + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ACQUIRE - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int find_key = _keys_in_use[rand_indy]; + _keys_in_use.remove(find_key); // remove the record of that key. + data_shuttle *found = _the_table.acquire(find_key); + ASSERT_NON_NULL(found, "key should be present when expected"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should not have zero length"); + WHACK(found); + found = _the_table.acquire(find_key); + ASSERT_NULL(found, "key should not be there after zap"); + return true; +} + +bool test_hash_table::test_find_zap_add() +{ + FUNCDEF("test_find_zap_add"); + // find. + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[FIND_ZAP_ADD - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + // this is another key list invariant function, if it works. + int find_key = _keys_in_use[rand_indy]; + data_shuttle *found = NIL; + ASSERT_TRUE(_the_table.find(find_key, found), "key should be locateable"); + ASSERT_NON_NULL(found, "key should not have NIL contents"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should be equal to real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should not have zero length"); + // zap. + ASSERT_TRUE(_the_table.zap(find_key), "should be able to zap the item we had found"); + // add. + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = find_key; + ASSERT_EQUAL(_the_table.add(find_key, to_add).value(), our_hash::IS_NEW, + "the item we zapped should be gone"); + return true; +} + +bool test_hash_table::test_reset(bool always_run) +{ + FUNCDEF("test_reset"); + if (!always_run) { + int maybe = randomizer().inclusive(1, 1000); + // this is hardly ever hit, but it loses all contents too often otherwise. + if ( (maybe > 372) || (maybe < 368) ) return true; + } + + // we hit the big time; we will reset now. + _hits[RESET - FIRST_TEST]++; + _the_table.reset(); + for (int i = _keys_in_use.elements() - 1; i >= 0; i--) { + int dead_key = _keys_in_use[i]; + ASSERT_FALSE(_the_table.acquire(dead_key), "after reset, we should not find item"); + _keys_in_use.remove(dead_key); + } + return true; +} + +//hmmm: implement these tests! + +bool test_hash_table::test_acquire_add_zap() +{ + _hits[ACQUIRE_ADD_ZAP - FIRST_TEST]++; +return false; +} + +bool test_hash_table::test_find_add_find() +{ + _hits[FIND_ADD_FIND - FIRST_TEST]++; +return false; +} + +bool test_hash_table::test_check_sanity() +{ + _hits[CHECK_SANITY - FIRST_TEST]++; +return false; +} + +////////////// + +HOOPLE_MAIN(test_hash_table, ) + diff --git a/nucleus/library/tests_structures/test_int_hash.cpp b/nucleus/library/tests_structures/test_int_hash.cpp new file mode 100644 index 00000000..1c888daa --- /dev/null +++ b/nucleus/library/tests_structures/test_int_hash.cpp @@ -0,0 +1,553 @@ +/* +* Name : test_int_hash +* Author : Chris Koeritz +* Purpose: +* Tests out hash_table specialization for integers. +** +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_INT_HASH + // uncomment for noisier run. + +#define EXTREME_CHECKING + // causes extra checks in the library code. + +const double TEST_DURATION = 0.5 * SECOND_ms; + +const int MAX_DEFAULT_BITS = 2; + // we start low since we will rehash occasionally. + +////////////// + +enum test_actions { + FIRST_TEST = 38, // place-holder. + ADD = FIRST_TEST, + // adds an item that is probably new. + ADD_ADD, + // adds an item that is probably new, followed by another item under the + // same key id. this ensures the overwriting gets tested. + ZAP, + // finds an item we know is in the list and whacks it. + ADD_ZAP, + // adds a new item and immediately finds and zaps it. + ZAP_ADD, + // zaps an item that we know about and then adds a new item with the same + // identifier. + FIND, + // locates an item in the list which we know should exist. + ACQUIRE, + // grabs an item out of the list (and tosses it). + FIND_ZAP_ADD, + // finds an item we know should exist, zaps it out of the list, then adds + // a new item with the same id. + ACQUIRE_ADD_ZAP, + // removes an item from the list that we know should be there, adds it back + // in, and then whacks it. + FIND_ADD_FIND, + // find an item with a particular id (one that we know should be in the + // list) and then adds a different item using the same id. the new item + // is then sought. + RESET, + // tosses all data out of the hash table. not done very often. + CHECK_SANITY, + // look for any problems or irregularities; print the contents of the list + // if any are found. + REHASH, + // resizes the hash table. + COPY, + // copies a hash table to another hash table. + LAST_TEST = COPY // place-holder; must equal test just prior. +}; + +////////////// + +// a simple object that is used as the contents of the hash_table. + +class data_shuttle +{ +public: + int food_bar; + astring snacky_string; + bool hungry; + byte_array chunk; + chaos chao; + + data_shuttle() + : snacky_string(string_manipulation::make_random_name()), + chunk(chao.inclusive(100, 10000)) {} +}; + +////////////// + +class test_int_hash : public virtual unit_base, virtual public application_shell +{ +public: + test_int_hash(); + + int execute(); + //!< the main startup for the test. + + bool perform_a_test(test_actions test_type); + //!< carries out the specifics of the "test_type". + + bool pick_a_test(); + //!< randomly picks one of the test types and performs it. + + DEFINE_CLASS_NAME("test_int_hash"); + + static bool equivalence_applier(const int &key, data_shuttle &item, void *dlink); + + static const char *test_name(test_actions test_type); + + int raw_random_id(); //!< returns an unvetted random number. + int unused_random_id(); //!< returns an unused (so far) random number. + + // these functions each perform one type of test, which their names indicate. + bool test_add(); + bool test_add_add(); + bool test_zap(); + bool test_add_zap(); + bool test_zap_add(); + bool test_find(); + bool test_acquire(); + bool test_find_zap_add(); + bool test_acquire_add_zap(); + bool test_find_add_find(); + bool test_reset(bool always_do_it = false); + bool test_check_sanity(); + bool test_copy(); + bool test_rehash(); + +private: + int_set _keys_in_use; // keys that we think are stored in the table. + int_hash _the_table; // our table under test. + int _hits[LAST_TEST - FIRST_TEST + 1]; // tracks our testing activities. + int _tested; // simple counter of number of test calls. +}; + +////////////// + +typedef int_hash our_hash; // cleans up somewhat. + +////////////// + +test_int_hash::test_int_hash() +: application_shell(), + _the_table(MAX_DEFAULT_BITS), + _tested(0) +{ + for (int i = FIRST_TEST; i <= LAST_TEST; i++) + _hits[i - FIRST_TEST] = 0; +} + +int test_int_hash::execute() +{ + time_stamp exit_time((int)TEST_DURATION); +//log(astring("before starting tests")); + while (time_stamp() < exit_time) { + pick_a_test(); + } + test_reset(true); // make sure we do this at least once. +#ifdef DEBUG_INT_HASH + log(a_sprintf("did %d tests.\n", _tested)); + log(astring("Test Activity:")); + for (int i = 0; i < LAST_TEST - FIRST_TEST + 1; i++) + log(astring(astring::SPRINTF, "%d (%s): %d hits", i + FIRST_TEST, + test_name(test_actions(i + FIRST_TEST)), _hits[i])); + log(a_sprintf("note that test %d will seldom be executed.", RESET)); +#endif + return final_report(); +} + +const char *test_int_hash::test_name(test_actions test_type) +{ + switch (test_type) { + case ADD: return "ADD"; + case ADD_ADD: return "ADD_ADD"; + case ZAP: return "ZAP"; + case ADD_ZAP: return "ADD_ZAP"; + case ZAP_ADD: return "ZAP_ADD"; + case FIND: return "FIND"; + case ACQUIRE: return "ACQUIRE"; + case FIND_ZAP_ADD: return "FIND_ZAP_ADD"; + case ACQUIRE_ADD_ZAP: return "ACQUIRE_ADD_ZAP"; + case FIND_ADD_FIND: return "FIND_ADD_FIND"; + case RESET: return "RESET"; + case COPY: return "COPY"; + case REHASH: return "REHASH"; + case CHECK_SANITY: return "CHECK_SANITY"; + default: return "UnknownTest"; + } +} + +bool test_int_hash::perform_a_test(test_actions test_type) +{ + FUNCDEF("perform_a_test"); + +// log(astring(test_name(test_type)) + " "); + + switch (test_type) { + case ADD: return test_add(); + case ADD_ADD: return test_add_add(); + case ZAP: return test_zap(); + case ADD_ZAP: return test_add_zap(); + case ZAP_ADD: return test_zap_add(); + case FIND: return test_find(); + case ACQUIRE: return test_acquire(); + case FIND_ZAP_ADD: return test_find_zap_add(); + case ACQUIRE_ADD_ZAP: return test_acquire_add_zap(); + case FIND_ADD_FIND: return test_find_add_find(); + case RESET: return test_reset(); + case COPY: return test_copy(); + case REHASH: return test_rehash(); + case CHECK_SANITY: return test_check_sanity(); + default: + ASSERT_TRUE(false, "there should be no missing case seen!"); + return false; // never gets here. + } +} + +bool test_int_hash::pick_a_test() +{ + _tested++; + return perform_a_test(test_actions(randomizer().inclusive(FIRST_TEST, LAST_TEST))); +} + +int test_int_hash::raw_random_id() +{ + return randomizer().inclusive(-MAXINT32 / 4, MAXINT32 / 4); +} + +int test_int_hash::unused_random_id() +{ + while (true) { + int checking = raw_random_id(); + if (!_keys_in_use.member(checking)) return checking; // got one. + } // keep going until we find unused id. +} + +bool test_int_hash::test_add() +{ + FUNCDEF("test_add"); + _hits[ADD - FIRST_TEST]++; + int random_id = raw_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + outcome wanting = common::IS_NEW; + if (_keys_in_use.member(random_id)) wanting = common::EXISTING; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), wanting.value(), + "adding key should work with right expectation"); + if (_keys_in_use.member(random_id)) + return true; // already was there so we replaced. + _keys_in_use.add(random_id); + return true; +} + +////////////// + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*dynamic_cast(application_shell::single_instance())) + +int_hash *_hang_on = NIL; + // must be set before calling the apply method. + +bool test_int_hash::equivalence_applier(const int &key, data_shuttle &item, void *dlink) +{ + FUNCDEF("equivalence_applier"); + ASSERT_TRUE(dlink, "should have been given name"); + if (!dlink) return false; // fail. + astring test_name = (char *)dlink; + +//application_shell::single_instance()->log(astring("after name check")); + + data_shuttle *found = _hang_on->find(key); + ASSERT_TRUE(found, test_name + ": should find equivalent entry in second list"); + if (!found) return false; // bail or we'll crash. + +//application_shell::single_instance()->log(astring("after finding")); + + ASSERT_EQUAL(item.food_bar, found->food_bar, test_name + ": food_bar should not differ"); + ASSERT_EQUAL(item.snacky_string, found->snacky_string, test_name + ": snacky_string should not differ"); + ASSERT_EQUAL(item.hungry, found->hungry, test_name + ": hungry should not differ"); + return true; +} + +#undef UNIT_BASE_THIS_OBJECT +#define UNIT_BASE_THIS_OBJECT (*this) + +////////////// + +bool test_int_hash::test_rehash() +{ + FUNCDEF("test_rehash"); + // we don't want to rehash too often; it is expensive. +// int maybe = randomizer().inclusive(1, 50); +// if (maybe < 32) return true; // not this time. + + _hang_on = &_the_table; // must do this first. + + _hits[REHASH - FIRST_TEST]++; + + int_hash table_copy(_the_table.estimated_elements()); + + _the_table.apply(equivalence_applier, (void *)func); + astring second_test_name(func); + second_test_name += " try 2"; + table_copy.apply(equivalence_applier, (void *)second_test_name.s()); + + if (!_the_table.elements()) return true; // nothing to do right now for comparisons. + +//log("copying table..."); + copy_hash_table(table_copy, _the_table); + // make a copy of the table. + + ASSERT_INEQUAL(0, table_copy.elements(), "copy shouldn't have unexpected absence of contents"); + +//log("rehashing table..."); + _the_table.rehash(randomizer().inclusive(1, 20)); + +//hmmm: need test of dehash function that reduces elements estimated. + +//log("comparing table..."); + astring third_test_name(func); + third_test_name += " try 3"; + table_copy.apply(equivalence_applier, (void *)third_test_name.s()); +//log("done copy and compare."); + + return true; +} + +bool test_int_hash::test_copy() +{ + FUNCDEF("test_copy"); + // we don't want to copy too often. it's a heavy operation. +// int maybe = randomizer().inclusive(1, 50); +// if (maybe > 16) return true; // not this time. + + _hang_on = &_the_table; // must do this first. + + _hits[COPY - FIRST_TEST]++; + + int_hash table_copy(MAX_DEFAULT_BITS); + +//log("copying table..."); + copy_hash_table(table_copy, _the_table); + // make a copy of the table. + +//log("comparing table..."); + table_copy.apply(equivalence_applier, (void *)func); +//log("done copy and compare."); + + return true; +} + +////////////// + +bool test_int_hash::test_add_add() +{ + FUNCDEF("test_add_add"); + _hits[ADD_ADD - FIRST_TEST]++; + int random_id = unused_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, "key should be new"); + _keys_in_use.add(random_id); + + // second add on same id. + data_shuttle *next_add = new data_shuttle; + next_add->snacky_string = string_manipulation::make_random_name(); + next_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, next_add).value(), our_hash::EXISTING, + "second add should not say first failed"); + + return true; +} + +bool test_int_hash::test_zap() +{ + FUNCDEF("test_zap"); + int maybe = randomizer().inclusive(1, 1000); + if (maybe > 500) return true; + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ZAP - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int dead_key = _keys_in_use[rand_indy]; + _keys_in_use.remove(dead_key); // remove the record of that key. + ASSERT_TRUE(_the_table.zap(dead_key), "zap should work on key"); + return true; +} + +bool test_int_hash::test_add_zap() +{ + FUNCDEF("test_add_zap"); + // add. + _hits[ADD_ZAP - FIRST_TEST]++; + int random_id = unused_random_id(); + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = random_id; + ASSERT_EQUAL(_the_table.add(random_id, to_add).value(), common::IS_NEW, "key is new before zap"); + // zap. + ASSERT_TRUE(_the_table.zap(random_id), "add then zap should remove the key"); + return true; +} + +bool test_int_hash::test_zap_add() +{ + FUNCDEF("test_zap_add"); + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ZAP_ADD - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + // in the end, the key list state won't be changed unless the test fails. + int dead_key = _keys_in_use[rand_indy]; + ASSERT_TRUE(_the_table.zap(dead_key), "key should be there for zapping"); + + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = dead_key; + outcome ret = _the_table.add(dead_key, to_add); + ASSERT_EQUAL(ret.value(), our_hash::IS_NEW, "key should not already be present somehow"); + return true; +} + +int functional_return(int to_pass) { + return to_pass; +} + +bool test_int_hash::test_find() +{ + FUNCDEF("test_find"); + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[FIND - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int find_key = _keys_in_use[rand_indy]; + data_shuttle *found = NIL; + ASSERT_TRUE(_the_table.find(functional_return(find_key), found), + "key should be there when we look"); + ASSERT_TRUE(_the_table.find(functional_return(find_key)), "find2: key be there when checked"); + ASSERT_TRUE(found, "when key is found contents should not be NIL"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should have some length"); + return true; +} + +bool test_int_hash::test_acquire() +{ + FUNCDEF("test_acquire"); + int maybe = randomizer().inclusive(1, 1000); + if (maybe > 750) return true; + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[ACQUIRE - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + int find_key = _keys_in_use[rand_indy]; + _keys_in_use.remove(find_key); // remove the record of that key. + data_shuttle *found = _the_table.acquire(find_key); + ASSERT_TRUE(found, "key should be there like clockwork"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should be same as real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should have some length"); + WHACK(found); + found = _the_table.acquire(find_key); + ASSERT_FALSE(found, "key should be missing after zap"); + return true; +} + +bool test_int_hash::test_find_zap_add() +{ + FUNCDEF("test_find_zap_add"); + // find. + if (!_keys_in_use.elements()) return false; // can't do it yet. + _hits[FIND_ZAP_ADD - FIRST_TEST]++; + int rand_indy = randomizer().inclusive(0, _keys_in_use.elements() - 1); + // this is another key list invariant function, if it works. + int find_key = _keys_in_use[rand_indy]; + data_shuttle *found = NIL; + ASSERT_TRUE(_the_table.find(find_key, found), "key should be there when sought"); + ASSERT_TRUE(_the_table.find(find_key), "find2: key should be there for us to find"); + ASSERT_TRUE(found, "found key should have non-NIL contents"); + ASSERT_EQUAL(found->food_bar, find_key, "stored key should have no differences from real key"); + ASSERT_TRUE(found->snacky_string.length(), "stored string should have non-zero length"); + // zap. + ASSERT_TRUE(_the_table.zap(find_key), "should be able to zap the item we had found"); + // add. + data_shuttle *to_add = new data_shuttle; + to_add->snacky_string = string_manipulation::make_random_name(); + to_add->food_bar = find_key; + ASSERT_EQUAL(_the_table.add(find_key, to_add).value(), our_hash::IS_NEW, + "the item we zapped should not still be there"); + return true; +} + +bool test_int_hash::test_reset(bool always_do_it) +{ + FUNCDEF("test_reset"); + if (!always_do_it) { + int maybe = randomizer().inclusive(1, 1000); + // this is hardly ever hit, but it loses all contents too often otherwise. + if ( (maybe > 372) || (maybe < 368) ) return true; + } + + // we hit the big time; we will reset now. + _hits[RESET - FIRST_TEST]++; + _the_table.reset(); + for (int i = _keys_in_use.elements() - 1; i >= 0; i--) { + int dead_key = _keys_in_use[i]; + ASSERT_FALSE(_the_table.acquire(dead_key), "after reset, we should not find an item"); + _keys_in_use.remove(dead_key); + } + return true; +} + +//hmmm: implement these tests! + +bool test_int_hash::test_acquire_add_zap() +{ + _hits[ACQUIRE_ADD_ZAP - FIRST_TEST]++; +return false; +} + +bool test_int_hash::test_find_add_find() +{ + _hits[FIND_ADD_FIND - FIRST_TEST]++; +return false; +} + +bool test_int_hash::test_check_sanity() +{ + _hits[CHECK_SANITY - FIRST_TEST]++; +return false; +} + +////////////// + +HOOPLE_MAIN(test_int_hash, ) + diff --git a/nucleus/library/tests_structures/test_matrix.cpp b/nucleus/library/tests_structures/test_matrix.cpp new file mode 100644 index 00000000..bb87e1e0 --- /dev/null +++ b/nucleus/library/tests_structures/test_matrix.cpp @@ -0,0 +1,401 @@ +/* +* Name : test_matrix +* Author : Chris Koeritz +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_MATRIX + // uncomment for noisier version. + +const int DIM_ROWS = 10; +const int DIM_COLS = 10; + +// fills the matrix "to_stuff" with the pure version of the exemplar. +#define STUFF_MATRIX(to_stuff, max_row, max_col) \ + to_stuff.reset(max_row, max_col); \ + for (int r = 0; r < max_row; r++) \ + for (int c = 0; c < max_col; c++) \ + to_stuff.put(r, c, test_pure.get(r, c)) + +////////////// + +// forward. +class my_int_matrix; + +////////////// + +// this class exhibits an old bug where the matrix was zeroing out its +// contents for a same size resize. the zeroing allowed hell to spew forth. +class diggulite +{ +public: + diggulite() {} + virtual ~diggulite() {} +}; + +////////////// + +class test_matrix : virtual public unit_base, virtual public application_shell +{ +public: + test_matrix(); + + DEFINE_CLASS_NAME("test_matrix"); + + void log(const astring &to_print) { application_shell::log(to_print); } + // override to avoid redoing all the logs here. + + int execute(); + // performs main body of test. + + static astring dump_matrix(const my_int_matrix &to_print); + // creates a nice form of the matrix "to_print". + + void print_matrix(const my_int_matrix &to_print); + // dumps "to_print" to the diagnostic output. + + void test_out_submatrix(const my_int_matrix &source); + //!< runs some tests on the submatrix() function. + + void test_out_redimension(); + //!< checks out the redimension() method for resizing the array. + + void test_out_resizing_virtual_objects(); + //!< checks that a matrix of non-simple objects doesn't have bad problems. + + void test_out_zapping(const my_int_matrix &test_pure); + //!< tries out zap operations. + + void test_out_inserting(const my_int_matrix &test_pure); + //!< checks the insert row and column methods. +}; + +////////////// + +class my_int_matrix : public int_matrix, virtual public hoople_standard +{ +public: + my_int_matrix(int r = 0, int c = 0) : int_matrix(r, c) {} + my_int_matrix(const int_matrix &init) : int_matrix(init) {} + + DEFINE_CLASS_NAME("my_int_matrix"); + + virtual bool equal_to(const equalizable &s2) const { + const my_int_matrix *sec = dynamic_cast(&s2); + if (!sec) return false; + if (rows() != sec->rows()) return false; + if (columns() != sec->columns()) return false; + for (int r = 0; r < this->rows(); r++) + for (int c = 0; c < this->columns(); c++) + if ((*this)[r][c] != (*sec)[r][c]) return false; + return true; + } + + virtual void text_form(base_string &state_fill) const { + state_fill.assign(test_matrix::dump_matrix(*this)); + } +}; + +////////////// + +test_matrix::test_matrix() : application_shell() {} + +astring test_matrix::dump_matrix(const my_int_matrix &to_print) +{ + astring text; + for (int t = 0; t < to_print.rows(); t++) { + text += astring(astring::SPRINTF, "[%d] ", t); + for (int c = 0; c < to_print.columns(); c++) + text += astring(astring::SPRINTF, "%03d ", int(to_print[t][c])); + text += parser_bits::platform_eol_to_chars(); + } + return text; +} + +void test_matrix::print_matrix(const my_int_matrix &to_print) +{ log(astring("\n") + dump_matrix(to_print)); } + +void test_matrix::test_out_submatrix(const my_int_matrix &source) +{ + FUNCDEF("test_out_submatrix") + my_int_matrix test2(source); + + for (int s = 0; s < DIM_ROWS; s++) + for (int c = 0; c < DIM_COLS; c++) + ASSERT_EQUAL(source[s][c], test2[s][c], "computed matrices should be same after copy"); + +#ifdef DEBUG_MATRIX + log("before submatrix:"); + print_matrix(test2); +#endif + my_int_matrix chunk(test2.submatrix(2, 3, 3, 2)); + my_int_matrix chunk_comparator(3, 2); + for (int r = 0; r < 3; r++) + for (int c = 0; c < 2; c++) + chunk_comparator[r][c] = test2[r+2][c+3]; + ASSERT_EQUAL(chunk, chunk_comparator, "submatrix should grab proper contents"); +#ifdef DEBUG_MATRIX + log("after submatrix, chunk of the matrix has:"); + print_matrix(chunk); +#endif +} + +void test_matrix::test_out_redimension() +{ + FUNCDEF("test_out_redimension") + my_int_matrix computed(7, 14); + for (int x1 = 0; x1 < 7; x1++) { + for (int y1 = 0; y1 < 14; y1++) { + if ( (x1 * y1) % 2) computed[x1][y1] = 1 + x1 * 100 + y1; + else computed.put(x1, y1, 1 + x1 * 100 + y1); + } + } + + for (int x2 = 6; x2 >= 0; x2--) { + for (int y2 = 13; y2 >= 0; y2--) { + ASSERT_EQUAL(computed[x2][y2], 1 + x2 * 100 + y2, + "computed matrix should have proper computed values"); + } + } + + computed.redimension(3, 5); + ASSERT_FALSE( (computed.rows() != 3) || (computed.columns() != 5), + "redimension should not get size wrong"); + for (int x3 = 2; x3 >= 0; x3--) { + for (int y3 = 4; y3 >= 0; y3--) { + ASSERT_EQUAL(computed[x3][y3], 1 + x3 * 100 + y3, + "computed matrix should still have right values"); + } + } + + computed.redimension(0, 0); + ASSERT_FALSE(computed.rows() || computed.columns(), + "redimension to zero should see matrix as empty"); + + computed.reset(12, 20); + ASSERT_FALSE( (computed.rows() != 12) || (computed.columns() != 20), + "resize should compute proper size"); +} + +void test_matrix::test_out_resizing_virtual_objects() +{ + FUNCDEF("test_out_resizing_virtual_objects") + // this test block ensures that the matrix doesn't blow up from certain + // resizing operations performed on a templated type that has a virtual + // destructor. + matrix grids; + grids.reset(); + grids.redimension ( 0, 1 ); + grids.redimension ( 1, 1 ); + grids.reset(1, 1); + ASSERT_TRUE(true, "no explosions should occur due to virtual contents"); +} + +void test_matrix::test_out_zapping(const my_int_matrix &test_pure) +{ + FUNCDEF("test_out_zapping") + // this block tests the zapping ops. + my_int_matrix test_zap; + STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); + +#ifdef DEBUG_MATRIX + log("matrix before zappage:"); + print_matrix(test_zap); +#endif + + my_int_matrix compare_1 = test_zap; + ASSERT_EQUAL(compare_1, test_zap, "assignment works right"); + test_zap.zap_row(5); + // make same changes but with different ops so we can compare. + for (int r = 6; r < DIM_ROWS; r++) + for (int c = 0; c < DIM_COLS; c++) + compare_1[r - 1][c] = compare_1[r][c]; + compare_1.zap_row(DIM_ROWS - 1); // lose the last row now. + ASSERT_EQUAL(compare_1, test_zap, "zapping should work regardless of path"); + +#ifdef DEBUG_MATRIX + log("matrix after zappage of row 5:"); + print_matrix(test_zap); +#endif + + // reset the array again. + STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); + my_int_matrix compare_2 = test_zap; + test_zap.zap_column(3); + // now make those same changes in our compare array. + for (int r = 0; r < DIM_ROWS; r++) + for (int c = 4; c < DIM_COLS; c++) + compare_2[r][c - 1] = compare_2[r][c]; + compare_2.zap_column(DIM_COLS - 1); // lose the last row now. + ASSERT_EQUAL(compare_2, test_zap, "second zapping should work regardless of path"); + +#ifdef DEBUG_MATRIX + log("matrix after zappage of column 3:"); + print_matrix(test_zap); +#endif + + // reset test_zap again. + STUFF_MATRIX(test_zap, DIM_ROWS, DIM_COLS); + my_int_matrix compare_3(test_zap.submatrix(1, 1, DIM_ROWS - 2, DIM_COLS - 2)); + test_zap.zap_column(0); + test_zap.zap_row(0); + test_zap.zap_row(test_zap.rows() - 1); + test_zap.zap_column(test_zap.columns() - 1); + ASSERT_EQUAL(test_zap, compare_3, "zapping and submatrix should compute same result"); + +#ifdef DEBUG_MATRIX + log("matrix after zap of row 0, col 0, last row, last col"); + print_matrix(test_zap); +#endif +} + +void test_matrix::test_out_inserting(const my_int_matrix &test_pure) +{ + FUNCDEF("test_out_inserting") + // this block tests the inserting ops. + my_int_matrix test_insert; + STUFF_MATRIX(test_insert, 4, 4); + +#ifdef DEBUG_MATRIX + log("matrix before inserting:"); + print_matrix(test_insert); +#endif + + my_int_matrix compare_1(test_insert); + test_insert.insert_row(2); + compare_1.insert_row(4); + for (int r = 3; r >= 2; r--) + for (int c = 0; c < 4; c++) + compare_1[r + 1][c] = compare_1[r][c]; + for (int c = 0; c < 4; c++) + compare_1[2][c] = 0; + ASSERT_EQUAL(test_insert, compare_1, "inserting row should create expected array"); + +#ifdef DEBUG_MATRIX + log("matrix after insert of row 2:"); + print_matrix(test_insert); +#endif + + // reset test_insert again. + STUFF_MATRIX(test_insert, 5, 6); + +#ifdef DEBUG_MATRIX + log("reset matrix before inserting:"); + print_matrix(test_insert); +#endif + + my_int_matrix compare_2(test_insert); + test_insert.insert_column(3); + compare_2.insert_column(6); + for (int r = 0; r < 5; r++) + for (int c = 5; c >= 3; c--) + compare_2[r][c + 1] = compare_2[r][c]; + for (int r = 0; r < 5; r++) + compare_2[r][3] = 0; + ASSERT_EQUAL(test_insert, compare_2, "inserting column should create expected array"); + +#ifdef DEBUG_MATRIX + log("matrix after insert of column 3:"); + print_matrix(test_insert); +#endif + + // reset test_insert again. + STUFF_MATRIX(test_insert, 3, 3); + my_int_matrix compare_3(5, 5); + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) + compare_3[r + 1][c + 1] = test_insert[r][c]; + for (int r = 0; r < 5; r++) { compare_3[r][0] = 0; compare_3[r][4] = 0; } + for (int c = 0; c < 5; c++) { compare_3[0][c] = 0; compare_3[4][c] = 0; } + +#ifdef DEBUG_MATRIX + log("matrix before inserting:"); + print_matrix(test_insert); +#endif + + test_insert.insert_column(0); + +#ifdef DEBUG_MATRIX + log("insert col at 0"); + print_matrix(test_insert); +#endif + + test_insert.insert_row(test_insert.rows()); + +#ifdef DEBUG_MATRIX + log("insert row at rows()"); + print_matrix(test_insert); +#endif + + test_insert.insert_column(test_insert.columns()); + +#ifdef DEBUG_MATRIX + log("insert col at cols()"); + print_matrix(test_insert); + log("insert row at 0..."); +#endif + + test_insert.insert_row(0); + + ASSERT_EQUAL(test_insert, compare_3, + "inserting some rows and columns should create expected array"); + +#ifdef DEBUG_MATRIX + log(astring("matrix after insert of col 0, last row, last col, row 0")); + print_matrix(test_insert); +#endif +} + +int test_matrix::execute() +{ + FUNCDEF("execute"); + + my_int_matrix test_pure(DIM_ROWS, DIM_COLS); // kept without modification. + for (int r = 0; r < DIM_ROWS; r++) + for (int c = 0; c < DIM_COLS; c++) + test_pure[r][c] = r * DIM_COLS + c; + + my_int_matrix test1 = test_pure; // first copy to work with. + + test1.reset(); + ASSERT_FALSE(test1.rows() || test1.columns(), "after reset matrix should be empty"); + + test_out_submatrix(test_pure); + + test_out_redimension(); + + test_out_resizing_virtual_objects(); + + test_out_zapping(test_pure); + + test_out_inserting(test_pure); + + return final_report(); +} + +HOOPLE_MAIN(test_matrix, ) + diff --git a/nucleus/library/tests_structures/test_memory_limiter.cpp b/nucleus/library/tests_structures/test_memory_limiter.cpp new file mode 100644 index 00000000..e0fec5eb --- /dev/null +++ b/nucleus/library/tests_structures/test_memory_limiter.cpp @@ -0,0 +1,154 @@ +/*****************************************************************************\ +* * +* Name : test_memory_limiter * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Tests that the memory_limiter is keeping track of the memory users * +* accurately. * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +//#define DEBUG_MEMORY_LIMITER + // uncomment for debugging version. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_MEMORY_LIMITER + #include +#endif + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +const int MAXIMUM_MEM_OVERALL = 1 * MEGABYTE; +const int MAXIMUM_MEM_PER_OWNER = 100 * KILOBYTE; + +const int RUN_TIME = .8 * SECOND_ms; + +////////////// + +class test_memory_limiter : virtual public unit_base, virtual public application_shell +{ +public: + test_memory_limiter() {} + DEFINE_CLASS_NAME("test_memory_limiter"); + virtual int execute(); +}; + +////////////// + +struct mem_record { + int parent; + int allocated; + + mem_record(int parent_in = 0, int allocated_in = 0) + : parent(parent_in), allocated(allocated_in) {} +}; + +struct memorial : array {}; + +////////////// + +int test_memory_limiter::execute() +{ + FUNCDEF("execute"); + time_stamp when_to_leave(RUN_TIME); + time_stamp start; + memorial wtc; + memory_limiter to_test(MAXIMUM_MEM_OVERALL, MAXIMUM_MEM_PER_OWNER); + int allocations = 0; + int deletions = 0; + basis::un_int total_allocated = 0; + basis::un_int total_deleted = 0; + while (time_stamp() < when_to_leave) { + int to_do = randomizer().inclusive(1, 100); + if (to_do < 50) { + // add a new record. + int alloc = randomizer().inclusive(1, 1 * MEGABYTE); +//isolate min max alloc + int parent = randomizer().inclusive(1, 120); +//isolate min max parents + + if (!to_test.okay_allocation(parent, alloc)) + continue; // no space right now. + wtc += mem_record(parent, alloc); + allocations++; + total_allocated += alloc; + } else if (to_do < 88) { + // remove an existing record. + if (!wtc.length()) continue; // nothing to remove. + int indy = randomizer().inclusive(0, wtc.length() - 1); + mem_record to_gone = wtc[indy]; + wtc.zap(indy, indy); + ASSERT_TRUE(to_test.record_deletion(to_gone.parent, to_gone.allocated), + "first case failed to record deletion!"); + deletions++; + total_deleted += to_gone.allocated; + } else { +//do something funky, like allocate part of one into another... + } + } + + // now clear everything left in our list. + for (int i = 0; i < wtc.length(); i++) { + mem_record to_gone = wtc[i]; + ASSERT_TRUE(to_test.record_deletion(to_gone.parent, to_gone.allocated), + "second case failed to record deletion!"); + deletions++; + total_deleted += to_gone.allocated; + } + + // now check that the memory limiter has returned to camber. + + ASSERT_FALSE(to_test.overall_usage(), "final checks: there is still memory in use!"); + + ASSERT_EQUAL(to_test.overall_space_left(), MAXIMUM_MEM_OVERALL, + "final checks: the free space is not correct!"); + + int_set remaining = to_test.individuals_listed(); + ASSERT_FALSE(remaining.elements(), "final checks: there were still uncleared individuals!"); + + time_stamp end; + + LOG("stats for this run:"); + LOG(astring(astring::SPRINTF, "\trun time %f ms", + end.value() - start.value())); + LOG(astring(astring::SPRINTF, "\tallocations %d, total memory allocated %d", + allocations, total_allocated)); + LOG(astring(astring::SPRINTF, "\tdeletions %d, total memory deleted %d", + deletions, total_deleted)); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_memory_limiter, ); + diff --git a/nucleus/library/tests_structures/test_packing.cpp b/nucleus/library/tests_structures/test_packing.cpp new file mode 100644 index 00000000..13e369d6 --- /dev/null +++ b/nucleus/library/tests_structures/test_packing.cpp @@ -0,0 +1,248 @@ +/* +* Name : test_object_packing +* Author : Chris Koeritz +** +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace unit_test; + +#define GENERATE_TEST_NAME(group, message) \ + (astring(group) + " test group: " + message) + +#define TRY_ON(type, value, group) { \ + byte_array temp_array; \ + attach(temp_array, type(value)); \ + type output; \ +/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ + ASSERT_TRUE(detach(temp_array, output), \ + GENERATE_TEST_NAME(group, "should unpack " #type " okay")); \ + ASSERT_TRUE(output == type(value), \ + GENERATE_TEST_NAME(group, #type " value should match")); \ + ASSERT_FALSE(temp_array.length(), \ + GENERATE_TEST_NAME(group, #type " detached should be empty")); \ +} + +#define TRY_ON_OBSCURE(type, value, group) { \ + byte_array temp_array; \ + obscure_attach(temp_array, type(value)); \ + type output; \ +/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ + ASSERT_TRUE(obscure_detach(temp_array, output), \ + GENERATE_TEST_NAME(group, "should obscure unpack " #type " okay")); \ + ASSERT_TRUE(output == type(value), \ + GENERATE_TEST_NAME(group, #type " value should obscure match")); \ + ASSERT_FALSE(temp_array.length(), \ + GENERATE_TEST_NAME(group, #type " obscure detached should be empty")); \ +} + +#define TRY_ON_F(type, value, group) { \ + byte_array temp_array; \ + attach(temp_array, type(value)); \ + type output; \ +/*log(astring(astring::SPRINTF, "parms are: type=%s value=%s group=%s", #type, #value, #group));*/ \ + ASSERT_TRUE(detach(temp_array, output), \ + GENERATE_TEST_NAME(group, "should unpack " #type " fine")); \ +/* double_plus a(output); \ + double_plus b(value); */ \ + /*double diff = maximum(output, value) - minimum(output, value);*/ \ + int exponent_1, exponent_2; \ + double mantissa_1 = frexp(output, &exponent_1); \ + double mantissa_2 = frexp(output, &exponent_2); \ + ASSERT_FALSE( (mantissa_1 != mantissa_2) || (exponent_1 != exponent_2), \ + GENERATE_TEST_NAME(group, #type " value should match just so")); \ + ASSERT_FALSE(temp_array.length(), \ + GENERATE_TEST_NAME(group, #type " detached should have no data left")); \ +} + +class test_object_packing : virtual public unit_base, virtual public application_shell +{ +public: + test_object_packing() : application_shell() {} + ~test_object_packing() {} + + DEFINE_CLASS_NAME("test_object_packing"); + + int execute(); +}; + +////////////// + +int test_object_packing::execute() +{ + FUNCDEF("execute"); + { + #define TEST "first" + TRY_ON(int, 2383, TEST); + TRY_ON(int, -18281, TEST); +// TRY_ON(long, 337628, TEST); +// TRY_ON(long, -987887, TEST); + TRY_ON(short, 12983, TEST); + TRY_ON(short, -32700, TEST); + TRY_ON(int, 2988384, TEST); + TRY_ON(int, 92982984, TEST); +// TRY_ON(un_long, 388745, TEST); +// TRY_ON(un_long, 993787, TEST); + TRY_ON(basis::un_short, 12983, TEST); + TRY_ON(basis::un_short, 48377, TEST); + TRY_ON_OBSCURE(un_int, -23948377, TEST); + TRY_ON_OBSCURE(un_int, 28938, TEST); + #undef TEST + } + { + #define TEST "second" + TRY_ON(int, 0, TEST); + TRY_ON(int, MAXINT32, TEST); + TRY_ON(int, MININT32, TEST); + TRY_ON(abyte, 0, TEST); + TRY_ON(abyte, MAXBYTE, TEST); + TRY_ON(abyte, MINBYTE, TEST); + TRY_ON(char, 0, TEST); + TRY_ON(char, MAXCHAR, TEST); + TRY_ON(char, MINCHAR, TEST); +// TRY_ON(long, 0, TEST); +// TRY_ON(long, MAXLONG, TEST); +// TRY_ON(long, MINLONG, TEST); + TRY_ON(short, 0, TEST); + TRY_ON(short, MAXINT16, TEST); + TRY_ON(short, MININT16, TEST); + TRY_ON(basis::un_int, 0, TEST); + un_int max_u_int = MAXINT32 | MININT32; + TRY_ON(basis::un_int, max_u_int, TEST); + TRY_ON(basis::un_int, max_u_int - 1, TEST); + TRY_ON(basis::un_int, max_u_int - 2, TEST); + TRY_ON(basis::un_int, max_u_int - 3, TEST); +// un_long max_u_long = MAXLONG | MINLONG; +// TRY_ON(un_long, 0, TEST); +// TRY_ON(un_long, max_u_long, TEST); +// TRY_ON(un_long, max_u_long - 1, TEST); +// TRY_ON(un_long, max_u_long - 2, TEST); +// TRY_ON(un_long, max_u_long - 3, TEST); + basis::un_short max_u_short = MAXINT16 | MININT16; + TRY_ON(basis::un_short, 0, TEST); + TRY_ON(basis::un_short, max_u_short, TEST); + TRY_ON(basis::un_short, max_u_short - 1, TEST); + TRY_ON(basis::un_short, max_u_short - 2, TEST); + TRY_ON(basis::un_short, max_u_short - 3, TEST); + #undef TEST + } + { + #define TEST "third" + // new bit for floating point packing. + TRY_ON_F(double, 0.0, TEST); + TRY_ON_F(double, 1.0, TEST); + TRY_ON_F(double, -1.0, TEST); + TRY_ON_F(double, 1.1, TEST); + TRY_ON_F(double, -1.1, TEST); + TRY_ON_F(double, 1983.293, TEST); + TRY_ON_F(double, -1983.293, TEST); + TRY_ON_F(double, 984.293e20, TEST); + TRY_ON_F(double, -984.293e31, TEST); + + const int MAX_FLOAT_ITERS = 100; + int iters = 0; + while (iters++ < MAX_FLOAT_ITERS) { + double dividend = randomizer().inclusive(1, MAXINT32 / 2); + double divisor = randomizer().inclusive(1, MAXINT32 / 2); + double multiplier = randomizer().inclusive(1, MAXINT32 / 2); + double rand_float = (dividend / divisor) * multiplier; + if (randomizer().inclusive(0, 1) == 1) + rand_float = -1.0 * rand_float; + TRY_ON_F(double, rand_float, "third--loop"); +//log(a_sprintf("%f", rand_float)); + } + #undef TEST + } + + { + #define TEST "fourth" + // new test for char * packing. + const char *tunnel_vision = "plants can make good friends."; + const char *fresnel_lense = "chimney sweeps carry some soot."; + const char *snoopy = "small white dog with black spots."; + byte_array stored; + int fregose = 38861; + double perky_doodle = 3799.283e10; + const char *emptyish = ""; + int jumboat = 998; + // now stuff the array with some things. + attach(stored, fregose); + attach(stored, tunnel_vision); + attach(stored, snoopy); + attach(stored, perky_doodle); + attach(stored, fresnel_lense); + attach(stored, emptyish); + attach(stored, jumboat); + // time to restore those contents. + astring tunnel_copy; + astring fresnel_copy; + astring snoopy_copy; + int freg_copy; + double perk_copy; + astring emp_copy; + int jum_copy; + ASSERT_TRUE(detach(stored, freg_copy), + GENERATE_TEST_NAME(TEST, "first int failed to unpack")); + ASSERT_TRUE(detach(stored, tunnel_copy), + GENERATE_TEST_NAME(TEST, "first string failed to unpack")); + ASSERT_TRUE(detach(stored, snoopy_copy), + GENERATE_TEST_NAME(TEST, "second string failed to unpack")); + ASSERT_TRUE(detach(stored, perk_copy), + GENERATE_TEST_NAME(TEST, "first double failed to unpack")); + ASSERT_TRUE(detach(stored, fresnel_copy), + GENERATE_TEST_NAME(TEST, "third string failed to unpack")); + ASSERT_TRUE(detach(stored, emp_copy), + GENERATE_TEST_NAME(TEST, "fourth string failed to unpack")); + ASSERT_TRUE(detach(stored, jum_copy), + GENERATE_TEST_NAME(TEST, "second int failed to unpack")); + // now test contents. + ASSERT_EQUAL(freg_copy, fregose, + GENERATE_TEST_NAME(TEST, "first int had wrong contents")); + ASSERT_EQUAL(tunnel_copy, astring(tunnel_vision), + GENERATE_TEST_NAME(TEST, "first string had wrong contents")); + ASSERT_EQUAL(snoopy_copy, astring(snoopy), + GENERATE_TEST_NAME(TEST, "second string had wrong contents")); + ASSERT_EQUAL(perk_copy, perky_doodle, + GENERATE_TEST_NAME(TEST, "first double had wrong contents")); + ASSERT_EQUAL(fresnel_copy, astring(fresnel_lense), + GENERATE_TEST_NAME(TEST, "third string had wrong contents")); + ASSERT_EQUAL(emp_copy, astring(emptyish), + GENERATE_TEST_NAME(TEST, "fourth string had wrong contents")); + ASSERT_EQUAL(jum_copy, jumboat, + GENERATE_TEST_NAME(TEST, "second int had wrong contents")); + ASSERT_FALSE(stored.length(), + GENERATE_TEST_NAME(TEST, "array still had contents after detaching")); + #undef TEST + } + +// critical_events::alert_message("packable:: works for those functions tested."); + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_object_packing, ); + diff --git a/nucleus/library/tests_structures/test_set.cpp b/nucleus/library/tests_structures/test_set.cpp new file mode 100644 index 00000000..06175059 --- /dev/null +++ b/nucleus/library/tests_structures/test_set.cpp @@ -0,0 +1,74 @@ +/*****************************************************************************\ +* * +* Name : test_set * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +class test_set : virtual public unit_base, virtual public application_shell +{ +public: + test_set() {} + DEFINE_CLASS_NAME("test_set"); + virtual int execute(); +}; + +int test_set::execute() +{ + FUNCDEF("execute"); + int_set fred; + ASSERT_TRUE(fred.empty(), "first empty check should work"); + ASSERT_TRUE(fred.add(23), "fred 1st add should go in"); + ASSERT_TRUE(fred.add(90123), "fred 2nd add should be okay"); + ASSERT_FALSE(fred.add(23), "fred 3rd add works fine"); + ASSERT_FALSE(fred.add(90123), "fred 4th add is good"); + ASSERT_FALSE(fred.empty(), "second empty check should work"); + ASSERT_TRUE(fred.non_empty(), "non_empty check should be right"); + + int_set gen; + ASSERT_TRUE(gen.add(13), "gen 1st add is okay"); + ASSERT_TRUE(gen.add(23), "gen 2nd add should be fine"); + ASSERT_TRUE(gen.add(8012), "gen 3rd add was good"); + + int_set intersect(gen.intersection(fred)); + ASSERT_EQUAL(intersect.elements(), 1, "intersection elements should be one"); + ASSERT_TRUE(intersect.member(23), "element should be present as 23"); + + int_set uni(gen.set_union(fred)); + ASSERT_EQUAL(uni.elements(), 4, "union elements should be correct"); + ASSERT_TRUE(uni.member(23), "first element we seek should be present"); + ASSERT_TRUE(uni.member(90123), "second element we seek should be present"); + ASSERT_TRUE(uni.member(13), "third element we seek should be present"); + ASSERT_TRUE(uni.member(8012), "fourth element we seek should be present"); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_set, ) + diff --git a/nucleus/library/tests_structures/test_stack.cpp b/nucleus/library/tests_structures/test_stack.cpp new file mode 100644 index 00000000..231931ec --- /dev/null +++ b/nucleus/library/tests_structures/test_stack.cpp @@ -0,0 +1,413 @@ +/* +* Name : t_stack +* Author : Chris Koeritz +* Purpose: +* Tests out the stack object with both flat objects (byte_array) but also +* deep objects (pointer to byte_array). Both bounded and unbounded stacks +* are tested for each. +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_STACK + #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) +#endif + +#include +#include + +using namespace application; +using namespace basis; +///using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//HOOPLE_STARTUP_CODE; + +//#define DEBUG_STACK + // uncomment for a noisier version. + +const int test_iterations = 40; + + +class test_stack : public virtual unit_base, public virtual application_shell +{ +public: + test_stack() {} + DEFINE_CLASS_NAME("test_stack"); + int execute(); + + void test_stack_with_objects(); + void test_stack_with_pointers(); + + void CHECK_STACK_RESULT(outcome retval, const astring &place); + byte_array generate_flat(const char *to_store); + byte_array *generate_deep(const char *to_store); + astring text_form(stack &s); + astring text_form(stack &s); +}; + +// CHECK_STACK_RESULT: a function that takes care of error testing for a +// stack command. if executing the command ends in full or empty, then +// that is reported. +void test_stack::CHECK_STACK_RESULT(outcome retval, const astring &place) +{ +#ifdef DEBUG_STACK + if (retval == common::IS_FULL) + LOG(astring(astring::SPRINTF, "returned IS_FULL at %s", place.s())) + else if (retval == common::IS_EMPTY) + LOG(astring(astring::SPRINTF, "returned IS_EMPTY at %s", place.s())); +#else + if (retval.value() || !place) {} +#endif +} + +byte_array test_stack::generate_flat(const char *to_store) +{ + byte_array to_return = byte_array(int(strlen(to_store) + 1), (abyte *)to_store); + return to_return; +} + +byte_array *test_stack::generate_deep(const char *to_store) +{ + byte_array *to_return = new byte_array(int(strlen(to_store) + 1), + (abyte *)to_store); + return to_return; +} + +astring test_stack::text_form(stack &s) +{ + astring to_return; + for (int i = 0; i < s.elements(); i++) { + to_return += astring(astring::SPRINTF, "#%d: %s\n", i, s[i]->observe()); + } + return to_return; +} + +astring test_stack::text_form(stack &s) +{ + astring to_return; + for (int i = 0; i < s.elements(); i++) { + to_return += astring(astring::SPRINTF, "#%d: %s\n", i, s[i].observe()); + } + return to_return; +} + +void test_stack::test_stack_with_objects() +{ + FUNCDEF("test_stack_with_objects"); + for (int qq = 0; qq < test_iterations; qq++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "index %d", qq)); +#endif + stack bounded_stack(3); + stack unlimited_stack(0); + chaos randomizer; + + { +#ifdef DEBUG_STACK + LOG("testing the bounded stack first:"); +#endif + CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the first line")), "first push"); + CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the second line")), "second push"); + CHECK_STACK_RESULT(bounded_stack.push(generate_flat("the final and third")), "third push"); + byte_array gend = generate_flat("this shouldn't work"); + ASSERT_EQUAL(bounded_stack.push(gend).value(), common::IS_FULL, + "the bounded stack push should catch IS_FULL"); +#ifdef DEBUG_STACK + LOG("pushing worked successfully..."); + LOG("printing the stack in element order"); +#endif + for (int i = 0; i < bounded_stack.size(); i++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "depth %d has %s.", i, + bounded_stack[i].observe())); +#endif + } +#ifdef DEBUG_STACK + LOG("now popping the stack all the way back."); +#endif + int full_size = bounded_stack.size(); + for (int j = 0; j < full_size; j++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "pop %d, stack size is %d, has %s", j+1, + bounded_stack.size(), bounded_stack.top().observe())); +#endif + byte_array found; + bounded_stack.acquire_pop(found); + astring got((char *)found.observe()); + switch (j) { + case 0: + ASSERT_EQUAL(got, astring("the final and third"), + "acquire_pop should have right contents at 2"); + break; + case 1: + ASSERT_EQUAL(got, astring("the second line"), + "acquire_pop should have right contents at 1"); + break; + case 2: + ASSERT_EQUAL(got, astring("the first line"), + "acquire_pop should have right contents at 0"); + break; + } + } + ASSERT_EQUAL(bounded_stack.pop().value(), common::IS_EMPTY, + "bounded pop should have right outcome"); + } + + { +#ifdef DEBUG_STACK + LOG("testing the unbounded stack now:"); +#endif + for (int j = 0; j < 24; j++) { + astring line(astring::SPRINTF, "{posn %d here}", j); + CHECK_STACK_RESULT(unlimited_stack.push(generate_flat(line.s())), "unbound push"); + } +#ifdef DEBUG_STACK + LOG("unbounded stack in element order:"); +#endif + for (int k = 0; k < unlimited_stack.size(); k++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "#%d is %s", k, unlimited_stack[k].observe())); +#endif + } +#ifdef DEBUG_STACK + LOG("now popping fresh order:"); +#endif + while (unlimited_stack.size()) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "size %d, content %s", + unlimited_stack.size(), unlimited_stack.top().observe())); +#endif + unlimited_stack.pop(); + } +#ifdef DEBUG_STACK + LOG(""); +#endif + ASSERT_EQUAL(unlimited_stack.pop().value(), common::IS_EMPTY, + "unlimited pop should return empty as expected"); +#ifdef DEBUG_STACK + LOG("\ +----------------------------------------------\n\ +both types of stack exercises were successful.\n\ +----------------------------------------------"); +#endif + } + +#ifdef DEBUG_STACK + LOG("now setting up some simple stacks..."); +#endif + + { +#ifdef DEBUG_STACK + LOG("bounded first..."); +#endif + stack bounder(randomizer.inclusive(30, 80)); +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "length of bounder is max %d, curr %d", bounder.elements(), bounder.size())); +#endif + int max = bounder.elements(); + for (int i = 0; i < max; i++) { + ASSERT_EQUAL(bounder.size(), i, "the bounder size should be in step with loop"); + int byte_array_size = randomizer.inclusive(259, 287); + byte_array to_stuff(byte_array_size); + astring visible(astring::SPRINTF, "entry %d...", i); + visible.stuff((char *)to_stuff.access(), visible.length() + 1); + for (int j = visible.length() + 1; j < to_stuff.length(); j++) + *(to_stuff.access() + j) = '\0'; +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "pushing index %d", i)); +#endif + outcome ret = bounder.push(to_stuff); + ASSERT_EQUAL(ret.value(), common::OKAY, "pushing should not fail in simple test"); + } + ASSERT_EQUAL(bounder.elements(), bounder.size(), "bounder set should see size and max same"); +#ifdef DEBUG_STACK + LOG("inverting:"); +#endif + bounder.invert(); +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "inverted is:\n%s", text_form(bounder).s())); +#endif + while (bounder.size()) bounder.pop(); + } + } +} + +void test_stack::test_stack_with_pointers() +{ + FUNCDEF("test_stack_with_pointers") + for (int qq = 0; qq < test_iterations; qq++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "index %d", qq)); +#endif + stack bounded_stack(3); + stack unlimited_stack(0); + chaos randomizer; + + { +#ifdef DEBUG_STACK + LOG("testing the bounded stack first:"); +#endif + CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the first line")), "first push"); + CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the second line")), "second push"); + CHECK_STACK_RESULT(bounded_stack.push(generate_deep("the final and third")), "third push"); + byte_array *gend = generate_deep("this shouldn't work"); + ASSERT_EQUAL(bounded_stack.push(gend).value(), common::IS_FULL, + "the bounded stack push should catch IS_FULL"); + delete gend; +#ifdef DEBUG_STACK + LOG("pushing worked successfully..."); + LOG("printing the stack in element order"); +#endif + for (int i = 0; i < bounded_stack.size(); i++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "depth %d has: %s", i, bounded_stack[i]->observe())); +#endif + } +#ifdef DEBUG_STACK + LOG("now popping the stack all the way back."); +#endif + int full_size = bounded_stack.size(); + for (int j = 0; j < full_size; j++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "pop %d, stack size is %d, has %s", j+1, bounded_stack.size(), bounded_stack.top()->observe())); +#endif + byte_array *found; + bounded_stack.acquire_pop(found); + ASSERT_TRUE(found, "acquire_pop test should not return nil"); + ASSERT_TRUE(found->observe(), "acquire_pop test should have non-nil observe"); + astring got((char *)found->observe()); + switch (j) { + case 0: + ASSERT_EQUAL(got, astring("the final and third"), + "popping should have right contents at 2"); + break; + case 1: + ASSERT_EQUAL(got, astring("the second line"), + "popping should have right contents at 1"); + break; + case 2: + ASSERT_EQUAL(got, astring("the first line"), + "popping should have right contents at 0"); + break; + } + delete found; + } + ASSERT_EQUAL(bounded_stack.pop().value(), common::IS_EMPTY, + "bounded pop failure in result"); + } + + { +#ifdef DEBUG_STACK + LOG("testing the unbounded stack now:"); +#endif + for (int j = 0; j < 24; j++) { + astring line(astring::SPRINTF, "{posn %d here}", j); + CHECK_STACK_RESULT(unlimited_stack.push(generate_deep(line.s())), "unbound push"); + } +#ifdef DEBUG_STACK + LOG("unbounded stack in element order:"); +#endif + for (int k = 0; k < unlimited_stack.size(); k++) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "#%d is %s", k, unlimited_stack[k]->observe())); +#endif + } +#ifdef DEBUG_STACK + LOG("\nnow popping order:"); +#endif + while (unlimited_stack.size()) { +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "size %d, content %s", unlimited_stack.size(), unlimited_stack.top()->observe())); +#endif + byte_array *to_zap = unlimited_stack.top(); + unlimited_stack.pop(); + delete to_zap; + // okay because the pointer was copied, and the reference from top + // is not being relied on. + } +#ifdef DEBUG_STACK + LOG(""); +#endif + ASSERT_EQUAL(unlimited_stack.pop().value(), common::IS_EMPTY, + "unlimited pop should return empty as expected"); +#ifdef DEBUG_STACK + LOG("\n\ +----------------------------------------------\n\ +both types of stack exercises were successful.\n\ +----------------------------------------------"); +#endif + } + +#ifdef DEBUG_STACK + LOG("now setting up some simple stacks..."); +#endif + + { +#ifdef DEBUG_STACK + LOG("bounded first..."); +#endif + stack bounder(randomizer.inclusive(30, 80)); +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "length of bounder is max %d, curr %d", + bounder.elements(), bounder.size())); +#endif + int max = bounder.elements(); + for (int i = 0; i < max; i++) { + ASSERT_EQUAL(bounder.size(), i, "bounder size should remain in step with loop"); + int byte_array_size = randomizer.inclusive(259, 287); + byte_array *to_stuff = new byte_array(byte_array(byte_array_size)); + astring visible(astring::SPRINTF, "entry %d...", i); + visible.stuff((char *)to_stuff->observe(), visible.length() + 1); + for (int j = visible.length() + 1; j < to_stuff->length(); j++) + *(to_stuff->access() + j) = '\0'; + ASSERT_EQUAL(bounder.push(to_stuff).value(), common::OKAY, + "pushing should not fail in bound simple test"); + } + ASSERT_EQUAL(bounder.elements(), bounder.size(), "bounder set must have size and max agree"); +#ifdef DEBUG_STACK + LOG("inverting:"); +#endif + bounder.invert(); +#ifdef DEBUG_STACK + LOG(astring(astring::SPRINTF, "inverted is:\n%s", text_form(bounder).s())); +#endif + while (bounder.size()) { + byte_array *to_zap = bounder.top(); + bounder.pop(); + delete to_zap; + } + } + } +} + +int test_stack::execute() +{ + test_stack_with_objects(); + test_stack_with_pointers(); + return final_report(); +} + +HOOPLE_MAIN(test_stack, ) + diff --git a/nucleus/library/tests_structures/test_string_table.cpp b/nucleus/library/tests_structures/test_string_table.cpp new file mode 100644 index 00000000..8eadeda2 --- /dev/null +++ b/nucleus/library/tests_structures/test_string_table.cpp @@ -0,0 +1,314 @@ +/*****************************************************************************\ +* * +* Name : test_string_table * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +///using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_SYMBOL_TABLE + // uncomment to get lots of noise out of base class. + +//#define DEBUG_STRING_TABLE + // uncomment for noisy version. + +//#define TEST_SIZE_TABLE + // uncomment for testing the size of a string_table. + +//#define OLD_TEST + // uncomment for the older version of symbol table. + +const int test_iterations = 25; + +const int FIND_ITERATIONS = 20; +const int MAXIMUM_RANDOM_ADDS = 50; + +const int TEST_SIZE_TABLE_COUNT = 10000; + // the number of string_table elements we create for checking how + // big one of them is. + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +double time_in_add = 0; +double time_in_find = 0; +double time_in_pack = 0; +double time_in_unpack = 0; +basis::un_int operations = 0; + +class test_string_table : public virtual application_shell, public virtual unit_base +{ +public: + test_string_table() {} + DEFINE_CLASS_NAME("test_string_table"); + + void ADD(string_table &syms, const astring &name, const astring &to_add); + void FIND(const string_table &syms, const astring &name, const astring &to_find); + + void test_1(); + + virtual int execute(); +}; + +////////////// + +void test_string_table::ADD(string_table &syms, const astring &name, const astring &to_add) +{ + FUNCDEF("ADD") +//LOG(astring("add of ") + name + " => " + to_add); + time_stamp start; + outcome added = syms.add(name, to_add); + operations++; + ASSERT_INEQUAL(added.value(), common::EXISTING, "should not already be in table"); + time_stamp end; + time_in_add += end.value() - start.value(); +} + +void test_string_table::FIND(const string_table &syms, const astring &name, const astring &to_find) +{ + FUNCDEF("FIND") + for (int i = 0; i < FIND_ITERATIONS; i++) { + time_stamp start; + astring *found = syms.find(name); + operations++; + time_stamp end; + time_in_find += end.value() - start.value(); + ASSERT_TRUE(found, "should be in table"); + ASSERT_EQUAL(*found, to_find, "string in table should be correct"); + } +} + +////////////// + +void test_string_table::test_1() +{ + FUNCDEF("test_string_table"); +#ifdef TEST_SIZE_TABLE + { + array symso(TEST_SIZE_TABLE_COUNT); + operations++; + guards::alert_message(astring(astring::SPRINTF, "we should effectively " + "have swamped out the size of other code in the program now. the size " + "should represent %d string_table instantiations. take the current " + "memory size (minus maybe 2 megs) and divide by %d and you will have " + "a fairly accurate cost for instantiating a string table. hit a key.", + TEST_SIZE_TABLE_COUNT, TEST_SIZE_TABLE_COUNT)); + #ifdef _CONSOLE + getchar(); + #elif defined(__UNIX__) + getchar(); + #endif + } +#endif + + string_table syms; + string_table new_syms; + string_table newer_syms; + operations += 3; + for (int qq = 0; qq < test_iterations; qq++) { + syms.reset(); // still could be costly. + operations++; +#ifdef DEBUG_STRING_TABLE + LOG(astring(astring::SPRINTF, "index %d", qq)); +#endif + astring freudname("blurgh"); + astring freud("Sigmund Freud was a very freaked dude."); + ADD(syms, freudname, freud); + astring borgname("borg"); + astring borg("You will be assimilated."); + ADD(syms, borgname, borg); + astring xname("X-Men"); + astring x("The great unknown superhero cartoon."); + ADD(syms, xname, x); + astring aname("fleeny-brickle"); + astring a("lallax menick publum."); + ADD(syms, aname, a); + astring axname("ax"); + astring ax("Lizzy Borden has a very large hatchet."); + ADD(syms, axname, ax); + astring bloinkname("urg."); + astring bloink("this is a short and stupid string"); + ADD(syms, bloinkname, bloink); + astring faxname("fax"); + astring fax("alligators in my teacup."); + ADD(syms, faxname, fax); + astring zname("eagle ovaries"); + astring z("malfeasors beware"); + ADD(syms, zname, z); + + FIND(syms, freudname, freud); + FIND(syms, borgname, borg); + FIND(syms, xname, x); + FIND(syms, aname, a); + FIND(syms, axname, ax); + FIND(syms, bloinkname, bloink); + FIND(syms, faxname, fax); + FIND(syms, zname, z); + + astring name; + astring content; + for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { + name = string_manipulation::make_random_name(40, 108); + content = string_manipulation::make_random_name(300, 1000); + ADD(syms, name, content); + FIND(syms, name, content); + } + + // test copying of the string tables. + string_table chronos = syms; + operations++; + { + string_table mary = syms; + operations++; + string_table june = mary; + operations++; + ASSERT_TRUE(mary == syms, "copy test should compare properly"); + operations++; + } + ASSERT_TRUE(syms == chronos, "copy test original should not be harmed"); + operations++; + + { + // test the bug we think we found in the operator =. + string_table fred; + fred.add("urp", "rarp"); + fred.add("hyurgh", "ralph"); + string_table med; + med.add("urp", "rarp"); + med.add("hyurgh", "ralph"); + fred = med; // the deadly assignment. + fred = med; // the deadly assignment. + fred = med; // the deadly assignment. + fred = med; // the deadly assignment. + fred.add("urp", "rarp"); + fred.add("gurp", "flaarp"); // a new entry. + astring *urp = fred.find("urp"); + astring *hyurgh = fred.find("hyurgh"); + ASSERT_TRUE(urp, "urp should not go missing"); + ASSERT_TRUE(hyurgh, "hyurgh should not go missing"); +#ifdef DEBUG_STRING_TABLE + LOG(astring("got urp as ") + (urp? *urp : "empty!!!!")); + LOG(astring("got hyurgh as ") + (hyurgh? *hyurgh : "empty!!!!")); +#endif + astring urp_val = fred[0]; + // AH HA! this one finds it. accessing via bracket or other methods + // that use the internal get() method will fail. + // if there is no outright crash, then the bug is gone. +#ifdef DEBUG_STRING_TABLE + LOG(astring("got urp_val as ") + (urp_val.t()? urp_val : "empty!!!!")); +#endif + } + +#ifdef DEBUG_STRING_TABLE +//// LOG(astring(astring::SPRINTF,"This is the symbol table before any manipulation\n%s", syms.text_form())); + LOG("now packing the symbol table..."); +#endif + byte_array packed_form; + time_stamp start; + syms.pack(packed_form); + operations++; + time_stamp end; + time_in_pack += end.value() - start.value(); +#ifdef DEBUG_STRING_TABLE + LOG("now unpacking from packed form"); +#endif + start.reset(); + ASSERT_TRUE(new_syms.unpack(packed_form), "unpack test should succeed in unpacking"); + operations++; + end.reset(); // click, off. + time_in_unpack += end.value() - start.value(); + +#ifdef DEBUG_STRING_TABLE +/// LOG(astring(astring::SPRINTF, "unpacked form has:\n%s", new_syms->text_form().s())); +#endif + ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables should be same"); + operations++; + +#ifdef DEBUG_STRING_TABLE + LOG("now deleting old symbol table..."); +#endif + +#ifdef DEBUG_STRING_TABLE +/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", new_syms->text_form().s())); + LOG("packing the symbol table again..."); +#endif + byte_array packed_again(0); + start.reset(); // click, on. + new_syms.pack(packed_again); + operations++; + end.reset(); // click, off. + time_in_pack += end.value() - start.value(); +#ifdef DEBUG_STRING_TABLE + LOG("now unpacking from packed form again..."); +#endif + start = time_stamp(); + newer_syms.unpack(packed_again); + operations++; + end = time_stamp(); + time_in_unpack += end.value() - start.value(); +#ifdef DEBUG_STRING_TABLE + LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping " + "it:\n%s", newer_syms.text_form().s())); +#endif + ASSERT_TRUE(new_syms == newer_syms, "unpacked test should not be different symbol tables"); + operations++; + +#ifdef DEBUG_STRING_TABLE + LOG("now deleting new and newer symbol table..."); +#endif + } +} + +////////////// + +int test_string_table::execute() +{ +#ifdef DEBUG_STRING_TABLE + LOG(astring("starting test: ") + time_stamp::notarize(false)); +#endif + test_1(); +#ifdef DEBUG_STRING_TABLE + LOG(astring("done test: ") + time_stamp::notarize(false)); + LOG(astring(astring::SPRINTF, "time in add=%f", time_in_add)); + LOG(astring(astring::SPRINTF, "time in find=%f", time_in_find)); + LOG(astring(astring::SPRINTF, "time in pack=%f", time_in_pack)); + LOG(astring(astring::SPRINTF, "time in unpack=%f", time_in_unpack)); + LOG(astring(astring::SPRINTF, "total operations=%u", operations)); +#endif + + return final_report(); +} + +HOOPLE_MAIN(test_string_table, ) + diff --git a/nucleus/library/tests_structures/test_symbol_table.cpp b/nucleus/library/tests_structures/test_symbol_table.cpp new file mode 100644 index 00000000..22535a8f --- /dev/null +++ b/nucleus/library/tests_structures/test_symbol_table.cpp @@ -0,0 +1,591 @@ +/* +* Name : test_symbol_table +* Author : Chris Koeritz +** +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +//#define DEBUG_SYMBOL_TABLE + // uncomment for noisy version. + +const int test_iterations = 4; + +const int FIND_ITERATIONS = 10; +const int MAXIMUM_RANDOM_ADDS = 20; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +//#define OLD_TEST + // uncomment for the older version of symbol table. + +double time_in_add = 0; +double time_in_dep_find = 0; +double time_in_new_find = 0; +double time_in_pack = 0; +double time_in_unpack = 0; +double time_in_copy = 0; + +////////////// + +class my_table_def : virtual public hoople_standard, virtual public symbol_table +///, virtual public equalizable +{ +public: + DEFINE_CLASS_NAME("my_table_def"); + + virtual void text_form(base_string &state_fill) const { + state_fill.assign(astring(class_name()) + ": uhhh not really implemented"); + } + + virtual bool equal_to(const equalizable &s2) const { + const my_table_def *to_compare = dynamic_cast(&s2); + if (!to_compare) return false; + if (symbols() != to_compare->symbols()) return false; + for (int i = 0; i < symbols(); i++) { + if (name(i) != to_compare->name(i)) return false; + if (operator [](i) != (*to_compare)[i]) return false; + } + return true; + } +}; + +////////////// + +// jethro is a simple object for containment below. +class jethro +{ +public: + astring _truck; + + bool operator ==(const jethro &tc) const { return tc._truck == _truck; } + bool operator !=(const jethro &tc) const { return !(*this == tc); } +}; + +////////////// + +// the test_content object is an object with proper copy constructor +// and assignment operator that also has deep contents. +class test_content : public packable +{ +public: + int _q; + astring _ted; + astring _jed; + int_array _ned; + matrix _med; + + test_content() : _q(9) {} + + test_content(const astring &ted, const astring &jed) : _q(4), _ted(ted), + _jed(jed) {} + +//hmmm: pack and unpack don't do everything. + void pack(byte_array &packed_form) const { + attach(packed_form, _q); + _ted.pack(packed_form); + _jed.pack(packed_form); + } + int packed_size() const { + return sizeof(_q) + _ted.packed_size() + _jed.packed_size(); + } + bool unpack(byte_array &packed_form) { + if (!detach(packed_form, _q)) return false; + if (!_ted.unpack(packed_form)) return false; + if (!_jed.unpack(packed_form)) return false; + return true; + } + + bool operator ==(const test_content &tc) const { + if (tc._q != _q) return false; + if (tc._ted != _ted) return false; + if (tc._jed != _jed) return false; + if (tc._ned.length() != _ned.length()) return false; + for (int i = 0; i < _ned.length(); i++) + if (tc._ned[i] != _ned[i]) return false; + + if (tc._med.rows() != _med.rows()) return false; + if (tc._med.columns() != _med.columns()) return false; + for (int c = 0; c < _med.columns(); c++) + for (int r = 0; r < _med.rows(); r++) + if (tc._med.get(r, c) != _med.get(r, c)) return false; + + return true; + } + bool operator !=(const test_content &tc) const { return !operator ==(tc); } + + operator int() const { return _q; } +}; + +////////////// + +class second_table_def : virtual public hoople_standard, virtual public symbol_table +{ +public: + DEFINE_CLASS_NAME("second_table_def") + + virtual void text_form(base_string &state_fill) const { + state_fill.assign(astring(class_name()) + ": uhhh not really implemented"); + } + + virtual bool equal_to(const equalizable &s2) const { + const second_table_def *to_compare = dynamic_cast(&s2); + if (symbols() != to_compare->symbols()) return false; + for (int i = 0; i < symbols(); i++) { + if ((*this)[i] != (*to_compare)[i]) return false; + } + return true; + } +}; + +////////////// + +class test_symbol_table : public virtual application_shell, public virtual unit_base +{ +public: + test_symbol_table() {} + DEFINE_CLASS_NAME("test_symbol_table"); + + void test_1(); + + virtual int execute(); + + void ADD(my_table_def &syms, const astring &name, const astring &to_add); + void FIND(const my_table_def &syms, const astring &name, const astring &to_find); + + void ADD2(second_table_def &syms, const astring &name, const test_content &to_add); + + void pack(byte_array &packed_form, const my_table_def &to_pack); + bool unpack(byte_array &packed_form, my_table_def &to_unpack); + + void pack(byte_array &packed_form, const second_table_def &to_pack); + bool unpack(byte_array &packed_form, second_table_def &to_unpack); + + void test_byte_table(); + void test_tc_table(); +}; + +////////////// + +void test_symbol_table::ADD(my_table_def &syms, const astring &name, const astring &to_add) +{ + FUNCDEF("ADD") + byte_array to_stuff(to_add.length() + 1, (abyte *)to_add.s()); + time_stamp start; + outcome added = syms.add(name, to_stuff); + ASSERT_EQUAL(added.value(), common::IS_NEW, "should not already be in table"); + time_stamp end; + time_in_add += end.value() - start.value(); + start.reset(); +#ifdef OLD_TEST + int indy = syms.find(name); + ASSERT_FALSE(negative(indy), "should be in table after add"); + end.reset(); + time_in_dep_find += end.value() - start.value(); + const byte_array *found = &syms[indy]; +#else + byte_array *found = syms.find(name); + ASSERT_TRUE(found, "really should be in table after add"); + end.reset(); + time_in_new_find += end.value() - start.value(); +#endif + ASSERT_EQUAL(*found, to_stuff, "value should be right in table after add"); +} + +void test_symbol_table::FIND(const my_table_def &syms, const astring &name, const astring &to_add) +{ + FUNCDEF("FIND") + byte_array to_stuff(to_add.length() + 1, (abyte *)to_add.s()); + for (int i = 0; i < FIND_ITERATIONS; i++) { + time_stamp start; +#ifdef OLD_TEST + // double the calls so we roughly match the other test. + int indy = syms.find(name); + ASSERT_FALSE(negative(indy), "should locate item in table"); + indy = syms.find(name); + ASSERT_FALSE(negative(indy), "second find should locate item in table"); + byte_array *found = &syms[indy]; + time_stamp end; + time_in_dep_find += end.value() - start.value(); +#else + int indy = syms.dep_find(name); + ASSERT_FALSE(negative(indy), "should locate item in table (dep_find)"); + time_stamp end; + time_in_dep_find += end.value() - start.value(); + start.reset(); + byte_array *found = syms.find(name); + ASSERT_TRUE(found, "second find should see item in table (new_find)"); + end.reset(); + time_in_new_find += end.value() - start.value(); +#endif + } +} + +void test_symbol_table::pack(byte_array &packed_form, const my_table_def &to_pack) +{ + attach(packed_form, to_pack.symbols()); + astring name; + byte_array content; + for (int i = 0; i < to_pack.symbols(); i++) { + to_pack.retrieve(i, name, content); + name.pack(packed_form); + attach(packed_form, content); + } +} + +bool test_symbol_table::unpack(byte_array &packed_form, my_table_def &to_unpack) +{ + to_unpack.reset(); + int syms = 0; + if (!detach(packed_form, syms)) return false; + astring name; + byte_array chunk; + for (int i = 0; i < syms; i++) { + if (!name.unpack(packed_form)) return false; + if (!detach(packed_form, chunk)) return false; + ADD(to_unpack, name, (char *)chunk.observe()); + } + return true; +} + +void test_symbol_table::pack(byte_array &packed_form, const second_table_def &to_pack) +{ + attach(packed_form, to_pack.symbols()); + astring name; + test_content content; + for (int i = 0; i < to_pack.symbols(); i++) { + to_pack.retrieve(i, name, content); + name.pack(packed_form); + content.pack(packed_form); + } +} + +bool test_symbol_table::unpack(byte_array &packed_form, second_table_def &to_unpack) +{ + to_unpack.reset(); + int syms = 0; + if (!detach(packed_form, syms)) return false; + astring name; + test_content chunk; + for (int i = 0; i < syms; i++) { + if (!name.unpack(packed_form)) return false; + if (!chunk.unpack(packed_form)) return false; + to_unpack.add(name, chunk); + } + return true; +} + +////////////// + +my_table_def creatapose() +{ + my_table_def to_return; + astring name; + astring content; + for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { + name = string_manipulation::make_random_name(40, 108); + content = string_manipulation::make_random_name(300, 1000); + byte_array to_stuff(content.length() + 1, (abyte *)content.s()); + to_return.add(name, to_stuff); + } + return to_return; +} + +////////////// + +void test_symbol_table::test_byte_table() +{ + FUNCDEF("test_byte_table") + my_table_def syms; + my_table_def new_syms; + my_table_def newer_syms; + for (int qq = 0; qq < test_iterations; qq++) { + syms.reset(); // still could be costly. +#ifdef DEBUG_SYMBOL_TABLE + LOG(astring(astring::SPRINTF, "index %d", qq)); +#endif + astring freudname("blurgh"); + astring freud("Sigmund Freud was a very freaked dude."); + ADD(syms, freudname, freud); + astring borgname("borg"); + astring borg("You will be assimilated."); + ADD(syms, borgname, borg); + astring xname("X-Men"); + astring x("The great unknown superhero cartoon."); + ADD(syms, xname, x); + astring aname("fleeny-brickle"); + astring a("lallax menick publum."); + ADD(syms, aname, a); + astring axname("ax"); + astring ax("Lizzy Borden has a very large hatchet."); + ADD(syms, axname, ax); + astring bloinkname("urg."); + astring bloink("this is a short and stupid string"); + ADD(syms, bloinkname, bloink); + astring faxname("fax"); + astring fax("alligators in my teacup."); + ADD(syms, faxname, fax); + astring zname("eagle ovaries"); + astring z("malfeasors beware"); + ADD(syms, zname, z); + + FIND(syms, freudname, freud); + FIND(syms, borgname, borg); + FIND(syms, xname, x); + FIND(syms, aname, a); + FIND(syms, axname, ax); + FIND(syms, bloinkname, bloink); + FIND(syms, faxname, fax); + FIND(syms, zname, z); + + astring name; + astring content; + for (int y = 0; y < MAXIMUM_RANDOM_ADDS; y++) { + name = string_manipulation::make_random_name(40, 108); + content = string_manipulation::make_random_name(300, 1000); + ADD(syms, name, content); + FIND(syms, name, content); + } + + // test copying the table. + time_stamp start; // click, on. + my_table_def copy1(syms); + { + my_table_def joe(copy1); + my_table_def joe2 = joe; + ASSERT_EQUAL(joe2, joe, "copy test A: symbol tables should be same"); + my_table_def joe3 = creatapose(); // on stack. + my_table_def joe4 = joe3; + my_table_def joe5 = joe4; + ASSERT_EQUAL(joe5, joe3, "copy test A2: symbol tables should be same"); + } + ASSERT_FALSE(! (syms == copy1), "copy test B: symbol tables should be same still"); + time_stamp end; + time_in_copy += end.value() - start.value(); + +#ifdef DEBUG_SYMBOL_TABLE +//// LOG(astring(astring::SPRINTF,"This is the symbol table before any manipulation\n%s", syms.text_form())); + LOG("now packing the symbol table..."); +#endif + +#ifdef DEBUG_SYMBOL_TABLE + LOG("now unpacking from packed form"); +#endif + byte_array packed_form; + pack(packed_form, syms); + ASSERT_TRUE(unpack(packed_form, new_syms), "unpack test should not fail to unpack"); + +#ifdef DEBUG_SYMBOL_TABLE +/// LOG(astring(astring::SPRINTF, "unpacked form has:\n%s", new_syms->text_form().s())); +#endif + ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables must be equal"); + +#ifdef DEBUG_SYMBOL_TABLE +/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", new_syms->text_form().s())); + LOG("packing the symbol table again..."); +#endif + byte_array packed_again(0); + start.reset(); // click, on. + pack(packed_again, new_syms); + end.reset(); // click, off. + time_in_pack += end.value() - start.value(); +#ifdef DEBUG_SYMBOL_TABLE + LOG("now unpacking from packed form again..."); +#endif + start = time_stamp(); + ASSERT_TRUE(unpack(packed_again, newer_syms), "newer unpacking should working be"); + end = time_stamp(); + time_in_unpack += end.value() - start.value(); +#ifdef DEBUG_SYMBOL_TABLE +/// LOG(astring(astring::SPRINTF, "got the unpacked form, and dumping it:\n%s", newer_syms->text_form().s())); +#endif + ASSERT_EQUAL(new_syms, newer_syms, + "unpacked test these just aren't getting it but should be same"); + } +} + +////////////// + +void test_symbol_table::ADD2(second_table_def &syms, const astring &name, + const test_content &to_add) +{ + FUNCDEF("ADD2") + time_stamp start; + outcome added = syms.add(name, to_add); + ASSERT_EQUAL(added.value(), common::IS_NEW, "new item should not already be in table"); + time_stamp end; + time_in_add += end.value() - start.value(); + start = time_stamp(); // reset start. +#ifdef OLD_TEST + int indy = syms.find(name); + ASSERT_FALSE(negative(indy), "item should be found after add"); + // refind to balance timing. + indy = syms.find(name); + ASSERT_FALSE(negative(indy), "item should be found after second add"); + end = time_stamp(); // reset end. + time_in_dep_find += end.value() - start.value(); +#else + int indy = syms.dep_find(name); + ASSERT_FALSE(negative(indy), "finding item after add should work"); + end = time_stamp(); // reset end. + time_in_dep_find += end.value() - start.value(); + start = time_stamp(); + test_content *found = syms.find(name); + ASSERT_TRUE(found, "item shouldn't be nil that we found"); + end = time_stamp(); // reset end. + time_in_new_find += end.value() - start.value(); +#endif + astring name_out; + test_content content_out; + if (syms.retrieve(indy, name_out, content_out) != common::OKAY) { + ASSERT_EQUAL(name_out, name, "name should be correct after retrieve"); + ASSERT_EQUAL(content_out, to_add, "content should be correct after retrieve"); + } +} + +////////////// + +void test_symbol_table::test_tc_table() +{ + FUNCDEF("test_tc_table") + second_table_def syms; + second_table_def new_syms; + second_table_def newer_syms; + for (int qq = 0; qq < test_iterations; qq++) { + syms.reset(); +#ifdef DEBUG_SYMBOL_TABLE + LOG(astring(astring::SPRINTF, "index %d", qq)); +#endif + astring freudname("blurgh"); + test_content freud("Sigmund Freud was a very freaked dude.", "flutenorf"); + ADD2(syms, freudname, freud); + astring borgname("borg"); + test_content borg("You will be assimilated.", "alabaster"); + ADD2(syms, borgname, borg); + astring xname("X-Men"); + test_content x("The great unknown superhero cartoon.", "somnambulist"); + ADD2(syms, xname, x); + astring aname("fleeny-brickle"); + test_content a("lallax menick publum.", "aglos bagnort pavlod"); + ADD2(syms, aname, a); + astring axname("ax"); + test_content ax("Lizzy Borden has a very large hatchet.", "chop"); + ADD2(syms, axname, ax); + astring bloinkname("urg."); + test_content bloink("this is a short and stupid string", "not that short"); + ADD2(syms, bloinkname, bloink); + astring faxname("fax"); + test_content fax("alligators in my teacup.", "lake placid"); + ADD2(syms, faxname, fax); + astring zname("eagle ovaries"); + test_content z("malfeasors beware", "endangered"); + ADD2(syms, zname, z); + + // test copying the table. + time_stamp start; + second_table_def copy1(syms); + { + second_table_def joe(copy1); + second_table_def joe2 = joe; + ASSERT_EQUAL(joe2, joe, "copy test C: should have same symbol tables"); + } + ASSERT_FALSE(! (syms == copy1), "copy test D: symbol tables shouldn't be different"); + time_stamp end; + time_in_copy += end.value() - start.value(); + +#ifdef DEBUG_SYMBOL_TABLE + astring texto; + syms.text_form(texto); + LOG(astring("This is the symbol table before any manipulation\n") + texto); + LOG("now packing the symbol table..."); +#endif + +#ifdef DEBUG_SYMBOL_TABLE + LOG("now unpacking from packed form"); +#endif + byte_array packed_form; + pack(packed_form, syms); + ASSERT_TRUE(unpack(packed_form, new_syms), "crikey all these unpacks should work"); +#ifdef DEBUG_SYMBOL_TABLE + new_syms.text_form(texto); + LOG(astring("unpacked form has:\n") + texto); +#endif + ASSERT_FALSE(! (syms == new_syms), "unpacked test symbol tables should be equivalent"); + +#ifdef DEBUG_SYMBOL_TABLE + new_syms.text_form(texto); + LOG(astring("got the unpacked form, and dumping it:\n") + texto); + LOG("packing the symbol table again..."); +#endif + byte_array packed_again(0); + pack(packed_again, new_syms); +#ifdef DEBUG_SYMBOL_TABLE + LOG("now unpacking from packed form again..."); +#endif + ASSERT_TRUE(unpack(packed_again, newer_syms), "unpacking should get back the goods"); +#ifdef DEBUG_SYMBOL_TABLE + newer_syms.text_form(texto); + LOG(astring("got the unpacked form, and dumping it:\n") + texto); +#endif + ASSERT_FALSE(! (new_syms == newer_syms), "unpacked test symbol tables should stay same"); + } +} + +////////////// + +int test_symbol_table::execute() +{ +#ifdef DEBUG_SYMBOL_TABLE + LOG(astring("starting test 1: ") + time_stamp::notarize(false)); +#endif + test_byte_table(); +#ifdef DEBUG_SYMBOL_TABLE + LOG(astring("done test 1: ") + time_stamp::notarize(false)); + LOG(astring("starting test 2: ") + time_stamp::notarize(false)); +#endif + + test_tc_table(); +#ifdef DEBUG_SYMBOL_TABLE + LOG(astring("done test 2: ") + time_stamp::notarize(false)); + LOG(astring(astring::SPRINTF, "time in add=%f", time_in_add)); + LOG(astring(astring::SPRINTF, "time in dep_find=%f", time_in_dep_find)); + LOG(astring(astring::SPRINTF, "time in new_find=%f", time_in_new_find)); + LOG(astring(astring::SPRINTF, "time in pack=%f", time_in_pack)); + LOG(astring(astring::SPRINTF, "time in unpack=%f", time_in_unpack)); +#endif + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_symbol_table, ) + diff --git a/nucleus/library/tests_structures/test_unique_id.cpp b/nucleus/library/tests_structures/test_unique_id.cpp new file mode 100644 index 00000000..d86f6b77 --- /dev/null +++ b/nucleus/library/tests_structures/test_unique_id.cpp @@ -0,0 +1,82 @@ +/*****************************************************************************\ +* * +* Name : t_unique_id * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_STACK + #define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), to_print) +#endif + +using namespace application; +using namespace basis; +///using namespace configuration; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#include +#include + +////////////// + +class test_unique_id : public virtual unit_base, public virtual application_shell +{ +public: + test_unique_id() {} + DEFINE_CLASS_NAME("test_unique_id"); + int execute(); +}; + +HOOPLE_MAIN(test_unique_id, ); + +////////////// + +int test_unique_id::execute() +{ + FUNCDEF("execute"); + unique_int ted(25); + unique_int jed; + + ASSERT_TRUE(ted.raw_id(), "testing non-zero should not claim was zero"); + ASSERT_TRUE(!jed, "testing zero should claim was zero"); + ASSERT_TRUE(!!ted, "testing non-zero doubled should not claim was zero"); + ASSERT_TRUE(!!!jed, "testing zero doubled should claim was zero"); + + unique_int med(25); + unique_int fed(0); + + ASSERT_EQUAL(med.raw_id(), ted.raw_id(), "testing equality 1 should have correct result"); + ASSERT_EQUAL(fed.raw_id(), jed.raw_id(), "testing equality 2 should have correct result"); + ASSERT_INEQUAL(med.raw_id(), jed.raw_id(), "testing equality 3 should have correct result"); + ASSERT_INEQUAL(fed.raw_id(), ted.raw_id(), "testing equality 4 should have correct result"); + + ASSERT_FALSE(med != ted, "equality operator 1 should have correct result"); + ASSERT_FALSE(fed != jed, "equality operator 2 should have correct result"); + ASSERT_FALSE(med == jed, "equality operator 3 should have correct result"); + ASSERT_FALSE(fed == ted, "equality operator 4 should have correct result"); + + return final_report(); +} + diff --git a/nucleus/library/tests_structures/test_version.cpp b/nucleus/library/tests_structures/test_version.cpp new file mode 100644 index 00000000..8a68ffab --- /dev/null +++ b/nucleus/library/tests_structures/test_version.cpp @@ -0,0 +1,86 @@ +/* +* Name : test_version +* Author : Chris Koeritz +** +* Copyright (c) 2009-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +class test_version : public virtual unit_base, virtual public application_shell +{ +public: + test_version() : application_shell() {} + DEFINE_CLASS_NAME("test_version"); + virtual int execute(); +}; + +int test_version::execute() +{ + FUNCDEF("execute"); + + version v1(5, 6, 138); + version v2(5, 7, 108); + ASSERT_TRUE((v1 < v2), "compare v1 < v2 should work properly"); + ASSERT_FALSE(v1 > v2, "compare v1 > v2 should work properly"); + ASSERT_FALSE(v1 == v2, "compare v1 == v2 should work properly"); + ASSERT_TRUE((v1 != v2), "compare v1 != v2 should work properly"); + + version v3(4, 6, 180); + ASSERT_TRUE((v3 < v1), "compare v3 < v1 should work properly"); + ASSERT_FALSE(v3 > v1, "compare v3 > v1 should work properly"); + ASSERT_FALSE(v3 == v1, "compare v3 == v1 should work properly"); + ASSERT_TRUE((v3 != v1), "compare v3 != v1 should work properly"); + ASSERT_TRUE((v3 < v2), "compare v3 < v2 should work properly"); + ASSERT_FALSE(v3 > v2, "compare v3 > v2 should work properly"); + ASSERT_FALSE(v3 == v2, "compare v3 == v2 should work properly"); + ASSERT_TRUE((v3 != v2), "compare v3 != v2 should work properly"); + ASSERT_FALSE(v1 < v3, "compare v1 < v3 should work properly"); + ASSERT_TRUE((v1 > v3), "compare v1 > v3 should work properly"); + ASSERT_FALSE(v1 == v3, "compare v1 == v3 should work properly"); + ASSERT_TRUE((v1 != v3), "compare v1 != v3 should work properly"); + ASSERT_FALSE(v2 < v3, "compare v2 < v3 should work properly"); + ASSERT_TRUE((v2 > v3), "compare v2 > v3 should work properly"); + ASSERT_FALSE(v2 == v3, "compare v2 == v3 should work properly"); + ASSERT_TRUE((v2 != v3), "compare v2 != v3 should work properly"); + + version v4(4, 6, 180); + ASSERT_FALSE(v3 < v4, "compare v3 < v4 should work properly"); + ASSERT_FALSE(v3 > v4, "compare v3 > v4 should work properly"); + ASSERT_TRUE((v3 == v4), "compare v3 == v4 should work properly"); + ASSERT_FALSE(v3 != v4, "compare v3 != v4 should work properly"); + ASSERT_FALSE(v4 < v3, "compare v4 < v3 should work properly"); + ASSERT_FALSE(v4 > v3, "compare v4 > v3 should work properly"); + ASSERT_TRUE((v4 == v3), "compare v4 == v3 should work properly"); + ASSERT_FALSE(v4 != v3, "compare v4 != v3 should work properly"); + + return final_report(); +} + +HOOPLE_MAIN(test_version, ) + diff --git a/nucleus/library/tests_textual/df_1.csv b/nucleus/library/tests_textual/df_1.csv new file mode 100644 index 00000000..9aa58dfc --- /dev/null +++ b/nucleus/library/tests_textual/df_1.csv @@ -0,0 +1,112 @@ +"1", "OK", "STRING", "Row0", +"2", "OK", "INT16", "722", +"3", "OK", "INT16", "363", +"4", "OK", "INT16", "992", +"5", "OK", "INT16", "665", +"6", "OK", "INT16", "454", +"7", "OK", "INT16", "431", +"8", "OK", "INT16", "460", +"101", "OK", "STRING", "Row1", +"102", "OK", "INT16", "805", +"103", "OK", "INT16", "50", +"104", "OK", "INT16", "715", +"105", "OK", "INT16", "992", +"106", "OK", "INT16", "449", +"107", "OK", "INT16", "430", +"108", "OK", "INT16", "463", +"201", "OK", "STRING", "Row2", +"202", "OK", "INT16", "84", +"203", "OK", "INT16", "957", +"204", "OK", "INT16", "666", +"205", "OK", "INT16", "379", +"206", "OK", "INT16", "616", +"207", "OK", "INT16", "305", +"208", "OK", "INT16", "214", +"301", "OK", "STRING", "Row3", +"302", "OK", "INT16", "479", +"303", "OK", "INT16", "620", +"304", "OK", "INT16", "621", +"305", "OK", "INT16", "146", +"306", "OK", "INT16", "699", +"307", "OK", "INT16", "440", +"308", "OK", "INT16", "577", +"401", "OK", "STRING", "Row4", +"402", "OK", "INT16", "150", +"403", "OK", "INT16", "287", +"404", "OK", "INT16", "108", +"405", "OK", "INT16", "69", +"406", "OK", "INT16", "298", +"407", "OK", "INT16", "947", +"408", "OK", "INT16", "504", +"501", "OK", "STRING", "Row5", +"502", "OK", "INT16", "305", +"503", "OK", "INT16", "46", +"504", "OK", "INT16", "695", +"505", "OK", "INT16", "892", +"506", "OK", "INT16", "645", +"507", "OK", "INT16", "698", +"508", "OK", "INT16", "467", +"601", "OK", "STRING", "Row6", +"602", "OK", "INT16", "384", +"603", "OK", "INT16", "65", +"604", "OK", "INT16", "710", +"605", "OK", "INT16", "279", +"606", "OK", "INT16", "12", +"607", "OK", "INT16", "157", +"608", "OK", "INT16", "386", +"701", "OK", "STRING", "Row7", +"702", "OK", "INT16", "299", +"703", "OK", "INT16", "656", +"704", "OK", "INT16", "665", +"705", "OK", "INT16", "950", +"706", "OK", "INT16", "79", +"707", "OK", "INT16", "44", +"708", "OK", "INT16", "413", +"801", "OK", "STRING", "Row8", +"802", "OK", "INT16", "938", +"803", "OK", "INT16", "19", +"804", "OK", "INT16", "592", +"805", "OK", "INT16", "145", +"806", "OK", "INT16", "574", +"807", "OK", "INT16", "671", +"808", "OK", "INT16", "564", +"901", "OK", "STRING", "Row9", +"902", "OK", "INT16", "221", +"903", "OK", "INT16", "162", +"904", "OK", "INT16", "203", +"905", "OK", "INT16", "304", +"906", "OK", "INT16", "617", +"907", "OK", "INT16", "158", +"908", "OK", "INT16", "631", +"1001", "OK", "STRING", "Row10", +"1002", "OK", "INT16", "844", +"1003", "OK", "INT16", "157", +"1004", "OK", "INT16", "634", +"1005", "OK", "INT16", "123", +"1006", "OK", "INT16", "64", +"1007", "OK", "INT16", "585", +"1008", "OK", "INT16", "510", +"1101", "OK", "STRING", "Row11", +"1102", "OK", "INT16", "999", +"1103", "OK", "INT16", "228", +"1104", "OK", "INT16", "261", +"1105", "OK", "INT16", "394", +"1106", "OK", "INT16", "123", +"1107", "OK", "INT16", "216", +"1108", "OK", "INT16", "161", +"1201", "OK", "STRING", "Row12", +"1202", "OK", "INT16", "206", +"1203", "OK", "INT16", "583", +"1204", "OK", "INT16", "524", +"1205", "OK", "INT16", "877", +"1206", "OK", "INT16", "18", +"1207", "OK", "INT16", "11", +"1208", "OK", "INT16", "800", +"1301", "OK", "STRING", "Row13", +"1302", "OK", "INT16", "385", +"1303", "OK", "INT16", "286", +"1304", "OK", "INT16", "727", +"1305", "OK", "INT16", "540", +"1306", "OK", "INT16", "277", +"1307", "OK", "INT16", "314", +"1308", "OK", "INT16", "363", diff --git a/nucleus/library/tests_textual/makefile b/nucleus/library/tests_textual/makefile new file mode 100644 index 00000000..0fcaf2d4 --- /dev/null +++ b/nucleus/library/tests_textual/makefile @@ -0,0 +1,15 @@ +include cpp/variables.def + +PROJECT = tests_textual +TYPE = test +LAST_TARGETS = copy_datafile +TARGETS = test_byte_format.exe test_parse_csv.exe test_splitter.exe test_xml_generator.exe +LOCAL_LIBS_USED = unit_test application loggers configuration textual timely filesystem \ + structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + +copy_datafile: + $(HIDER)cp -f df_1.csv $(EXECUTABLE_DIR) + diff --git a/nucleus/library/tests_textual/test_byte_format.cpp b/nucleus/library/tests_textual/test_byte_format.cpp new file mode 100644 index 00000000..0895cc44 --- /dev/null +++ b/nucleus/library/tests_textual/test_byte_format.cpp @@ -0,0 +1,171 @@ +/* +* Name : test_byte_format +* Author : Chris Koeritz +* Purpose: Puts the byte formatting utilities through their paces. +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger::get(), astring(to_print)) + +class test_byte_format : virtual public unit_base, virtual public application_shell +{ +public: + test_byte_format() {} + DEFINE_CLASS_NAME("test_byte_format"); + int execute(); +}; + +int test_byte_format::execute() +{ + FUNCDEF("execute"); + { + // test out the byte shifting functions on a simple known array. + byte_array source; + source += 0x23; source += 0x5f; source += 0xa8; source += 0x2d; + source += 0xe2; source += 0x61; source += 0x90; source += 0x2d; + source += 0xf2; source += 0x38; + astring shifty; + byte_formatter::bytes_to_shifted_string(source, shifty); +// LOG(a_sprintf("A: shifted our %d bytes into: ", source.length()) +// + shifty); + byte_array source_copy; + byte_formatter::shifted_string_to_bytes(shifty, source_copy); + ASSERT_EQUAL(source_copy, source, "A: shifting corrupted the bytes."); + } + + { + // test out the byte shifting functions on a random array. + for (int run = 0; run < 100; run++) { + byte_array source; + int size = randomizer().inclusive(1, 20923); + for (int i = 0; i < size; i++) { + source += abyte(randomizer().inclusive(0, 255)); + } + astring shifty; + byte_formatter::bytes_to_shifted_string(source, shifty); +// LOG(a_sprintf("B: shifted our %d bytes into: ", source.length()) +// + shifty); + byte_array source_copy; + byte_formatter::shifted_string_to_bytes(shifty, source_copy); + ASSERT_EQUAL(source_copy, source, "B: shifting corrupted the bytes."); + } + } + + { + astring burf("alia bodalia petunia"); + astring dump(byte_formatter::text_dump((abyte *)burf.s(), burf.length() + 1)); +//doofus! make this compare the output with expectations. + LOG("dumped form is:"); + LOG(""); + LOG(dump); + } + { + abyte fodder[] = { 0x83, 0x0d, 0x93, 0x21, 0x82, 0xfe, 0xef, 0xdc, 0xb9, + 0xa9, 0x21, 0x54, 0x83, 0x38, 0x65, 0x59, 0x99, 0xff, 0x00, 0xa0, + 0x29, 0x03 }; + byte_array fred(sizeof(fodder), fodder); + astring as_text1; + byte_formatter::bytes_to_string(fred, as_text1, true); + LOG(astring("got string #1 of: ") + as_text1); + astring as_text2; + byte_formatter::bytes_to_string(fred, as_text2, false); + LOG(astring("got string #2 of: ") + as_text2); + byte_array convert1; + byte_formatter::string_to_bytes(as_text1, convert1); + byte_array convert2; + byte_formatter::string_to_bytes(as_text2, convert2); + ASSERT_EQUAL(fred, convert1, "first abyte conversion: failed due to inequality"); + ASSERT_TRUE(fred == convert2, "second abyte conversion: failed due to inequality"); + // now a harder test. + astring as_text3("muggulo x83d x93, x21, x82, xfe, xef, xdc, xb9, " + "xa9, x21, x54, x83, x38, x65, x59, x99, xff, x00a0, x293"); + byte_array harder_convert1; + byte_formatter::string_to_bytes(as_text3, harder_convert1); +astring back3; +byte_formatter::bytes_to_string(harder_convert1, back3); +LOG(astring("got third: ") + back3); + + astring as_text4("muggulo x83d x93, x21, x82, xfe, xef, xdc, xb9, " + "xa9, x21, x54, x83, x38, x65, x59, x99, xff, x00a0, x293gikkor"); + byte_array harder_convert2; + byte_formatter::string_to_bytes(as_text4, harder_convert2); +astring back4; +byte_formatter::bytes_to_string(harder_convert2, back4); +LOG(astring("got fourth: ") + back4); + ASSERT_EQUAL(fred, harder_convert1, "third abyte conversion: failed due to inequality"); + ASSERT_EQUAL(fred, harder_convert2, "fourth abyte conversion: failed due to inequality"); + + abyte fodder2[] = { +0x04, 0x00, 0x06, 0x00, 0x0a, 0x02, 0x03, 0x00, 0x06, 0x00, 0x48, 0x01, 0x1c, 0x00, 0x2c, 0x00, 0x04, 0x00, 0x09, 0x00, 0x17, 0x00, 0xff, 0xff, 0x00, 0x00, +0x00, 0x00, 0x09, 0x00 }; + fred = byte_array(sizeof(fodder2), fodder2); + astring as_text5("040006000a020300060048011c002c00040009001700ffff000000000900"); + byte_array harder_convert3; + byte_formatter::string_to_bytes(as_text5, harder_convert3); +astring back5; +byte_formatter::bytes_to_string(harder_convert3, back5); +LOG(astring("got fifth: ") + back5); + ASSERT_EQUAL(fred, harder_convert3, "fifth abyte conversion: failed due to inequality"); + +#ifndef EMBEDDED_BUILD + // now a test of parse_dump. + astring fred_dump; + byte_formatter::text_dump(fred_dump, fred, 0x993834); +LOG("fred dump..."); +LOG(fred_dump); + byte_array fred_like; + byte_formatter::parse_dump(fred_dump, fred_like); + ASSERT_EQUAL(fred, fred_like, "parse_dump test: failed due to inequality"); +#endif + } + { + // an endian tester. + basis::un_short test1 = 0x3c5f; + LOG("0x3c5f in intel:"); + LOG(byte_formatter::text_dump((abyte *)&test1, 2)); + basis::un_int test2 = 0x9eaad0cb; + LOG("0x9eaad0cb in intel:"); + LOG(byte_formatter::text_dump((abyte *)&test2, 4)); + + // now see what they look like as packables. + byte_array testa; + structures::attach(testa, test1); + LOG("0x3c5f in package:"); + LOG(byte_formatter::text_dump(testa)); + byte_array testb; + structures::attach(testb, test2); + LOG("0x9eaad0cb in package:"); + LOG(byte_formatter::text_dump(testb)); + } + + return final_report(); +} + +HOOPLE_MAIN(test_byte_format, ); + diff --git a/nucleus/library/tests_textual/test_parse_csv.cpp b/nucleus/library/tests_textual/test_parse_csv.cpp new file mode 100644 index 00000000..99c7b5b8 --- /dev/null +++ b/nucleus/library/tests_textual/test_parse_csv.cpp @@ -0,0 +1,176 @@ +/* +* Name : test parsing of csv +* Author : Chris Koeritz +* Purpose: Checks that the CSV parsing function handles a few common scenarios. +** +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) + +// the number of times we scan our data file for performance test. +const int MAX_DATA_FILE_ITERS = 4000; + +class test_parsing_csv : public virtual unit_base, public virtual application_shell +{ +public: + test_parsing_csv() {} + DEFINE_CLASS_NAME("test_parsing_csv"); + int execute(); +}; + +//hmmm: too congratulatory? +#define COMPLAIN_FIELD(list, index, value) \ + ASSERT_EQUAL(list[index], astring(value), \ + a_sprintf("comparison test should have field %d correct in %s", index, #list)) + +int test_parsing_csv::execute() +{ + FUNCDEF("execute"); + astring line1 = "\"fupe\",\"snoorp\",\"lutem\",\"fipe\""; + string_array fields1; + bool works1 = list_parsing::parse_csv_line(line1, fields1); + ASSERT_TRUE(works1, "first test should not fail to parse"); +//LOG(a_sprintf("fields len now %d", fields1.length())); + ASSERT_EQUAL(fields1.length(), 4, "first test should have right count of strings found"); + COMPLAIN_FIELD(fields1, 0, "fupe"); + COMPLAIN_FIELD(fields1, 1, "snoorp"); + COMPLAIN_FIELD(fields1, 2, "lutem"); + COMPLAIN_FIELD(fields1, 3, "fipe"); + + astring line2 = "fupe,\"snoorp\",lutem,\"fipe\""; + string_array fields2; + bool works2 = list_parsing::parse_csv_line(line2, fields2); + ASSERT_TRUE(works2, "second test should not fail to parse"); + ASSERT_EQUAL(fields2.length(), 4, "second test should have right count of strings found"); + COMPLAIN_FIELD(fields2, 0, "fupe"); + COMPLAIN_FIELD(fields2, 1, "snoorp"); + COMPLAIN_FIELD(fields2, 2, "lutem"); + COMPLAIN_FIELD(fields2, 3, "fipe"); + + astring line3 = "\"lowenburger\",\"wazizzle\",morphel"; + string_array fields3; + bool works3 = list_parsing::parse_csv_line(line3, fields3); + ASSERT_TRUE(works3, "third test should not fail to parse"); + ASSERT_EQUAL(fields3.length(), 3, "third test should have right count of strings found"); + COMPLAIN_FIELD(fields3, 0, "lowenburger"); + COMPLAIN_FIELD(fields3, 1, "wazizzle"); + COMPLAIN_FIELD(fields3, 2, "morphel"); + + astring line4 = "\"lowenburger\",\"wazizzle\",morphel,"; + string_array fields4; + bool works4 = list_parsing::parse_csv_line(line4, fields4); + ASSERT_TRUE(works4, "fourth test should not fail to parse"); + ASSERT_EQUAL(fields4.length(), 4, "fourth test should not have wrong count of strings found"); + COMPLAIN_FIELD(fields4, 0, "lowenburger"); + COMPLAIN_FIELD(fields4, 1, "wazizzle"); + COMPLAIN_FIELD(fields4, 2, "morphel"); + COMPLAIN_FIELD(fields4, 3, ""); + + astring line5 = "\"lowenburger\",,"; + string_array fields5; + bool works5 = list_parsing::parse_csv_line(line5, fields5); + ASSERT_TRUE(works5, "fifth test should not fail to parse"); + ASSERT_EQUAL(fields5.length(), 3, "fifth test should have right count of strings found"); + COMPLAIN_FIELD(fields5, 0, "lowenburger"); + COMPLAIN_FIELD(fields5, 1, ""); + COMPLAIN_FIELD(fields5, 2, ""); + + astring line6 = ",,,\"rasputy\",,\"spunk\",ralph"; + string_array fields6; + bool works6 = list_parsing::parse_csv_line(line6, fields6); + ASSERT_TRUE(works6, "sixth test should not fail to parse"); + ASSERT_EQUAL(fields6.length(), 7, "sixth test should have right count of strings found"); + COMPLAIN_FIELD(fields6, 0, ""); + COMPLAIN_FIELD(fields6, 1, ""); + COMPLAIN_FIELD(fields6, 2, ""); + COMPLAIN_FIELD(fields6, 3, "rasputy"); + COMPLAIN_FIELD(fields6, 4, ""); + COMPLAIN_FIELD(fields6, 5, "spunk"); + COMPLAIN_FIELD(fields6, 6, "ralph"); + + astring line7 = "\"SRV0001337CHN0000001DSP0000001SRV0001337LAY0003108,16,0,8,192\",\"\\\"row_3\\\" on 12.5.55.159\",3"; + string_array fields7; + bool works7 = list_parsing::parse_csv_line(line7, fields7); + ASSERT_TRUE(works7, "seventh test should not fail to parse"); + ASSERT_EQUAL(fields7.length(), 3, "seventh test should have right count of strings found"); + COMPLAIN_FIELD(fields7, 0, "SRV0001337CHN0000001DSP0000001SRV0001337LAY0003108,16,0,8,192"); + COMPLAIN_FIELD(fields7, 1, "\"row_3\" on 12.5.55.159"); + COMPLAIN_FIELD(fields7, 2, "3"); + + // test 8... use data file. + filename df_dir = filename(application_configuration::application_name()).dirname(); + byte_filer test_data(df_dir.raw() + "/df_1.csv", "rb"); + string_array parsed; + string_array lines; + astring curr_line; + int read_result; + while ( (read_result = test_data.getline(curr_line, 1024)) > 0 ) + lines += curr_line; + if (lines.length()) { + // now we have the data file loaded. + stopwatch clicker; + clicker.start(); + for (int iterations = 0; iterations < MAX_DATA_FILE_ITERS; iterations++) { + for (int line = 0; line < lines.length(); line++) { + const astring ¤t = lines[line]; + list_parsing::parse_csv_line(current, parsed); + } + } + clicker.stop(); + log(a_sprintf("%d csv lines with %d iterations took %d ms (or %d s).", + lines.length(), MAX_DATA_FILE_ITERS, clicker.elapsed(), + clicker.elapsed() / 1000)); + } + + // test 9: process results of create_csv_line. + string_array fields9; + fields9 += "ACk\"boozort"; + fields9 += "sme\"ra\"\"foop"; + fields9 += "\"gumby\""; + astring line9 = "\"ACk\\\"boozort\",\"sme\\\"ra\\\"\\\"foop\",\"\\\"gumby\\\"\""; + astring gen_line_9; + list_parsing::create_csv_line(fields9, gen_line_9); +//log(astring(" got gen line: ") + gen_line_9); +//log(astring("expected line: ") + line9); + ASSERT_EQUAL(gen_line_9, line9, "ninth test should not fail to create expected text"); + string_array fields9_b; + bool works9 = list_parsing::parse_csv_line(gen_line_9, fields9_b); + ASSERT_TRUE(works9, "ninth test should not fail to parse"); + ASSERT_TRUE(fields9_b == fields9, "ninth test should match original fields"); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_parsing_csv, ) + diff --git a/nucleus/library/tests_textual/test_splitter.cpp b/nucleus/library/tests_textual/test_splitter.cpp new file mode 100644 index 00000000..be07b62b --- /dev/null +++ b/nucleus/library/tests_textual/test_splitter.cpp @@ -0,0 +1,96 @@ +/* +* Name : test_splitter +* Author : Chris Koeritz +* Purpose: Checks out the line splitting support to make sure it is working. +** +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(to_print) EMERGENCY_LOG(program_wide_logger().get(), astring(to_print)) + +class test_splitter : public virtual unit_base, virtual public application_shell +{ +public: + test_splitter() {} + DEFINE_CLASS_NAME("test_splitter"); + int execute(); +}; + +astring show_limits(int min_col, int max_col) +{ + astring to_return; + for (int i = 0; i <= max_col; i++) { + if (i < min_col) to_return += " "; + else to_return += "+"; + } + return to_return; +} + +#define SHOW_SPLIT(str, low, high) \ + string_manipulation::split_lines(str, output, low, high); \ + LOG(""); formal(temp)\ + LOG(show_limits(low, high)); \ + LOG(output + "<<<<"); \ + LOG(show_limits(low, high)) + +int test_splitter::execute() +{ + FUNCDEF("execute"); + + astring split_1 = "This is a fairly long paragraph that will be split a few different ways to check the splitting logic. It will be interesting to see how things like spaces, and punctuation, are handled."; + + astring output; + + SHOW_SPLIT(split_1, 0, 1); + SHOW_SPLIT(split_1, 0, 10); + SHOW_SPLIT(split_1, 10, 30); + SHOW_SPLIT(split_1, 20, 35); + SHOW_SPLIT(split_1, 4, 50); + SHOW_SPLIT(split_1, 8, 12); + + astring split_2 = "Here's another string.\r\nThere are embedded carriage returns after every sentence.\r\nSo, this should be a good test of how those things are handled when they're seen in the body of the text.\r\nThe next one is, hey, guess what?\r\nIt's a simple LF instead of CRLF; here it comes:\nHow did that look compared the others?\nThe previous was another bare one.\r\nWhen can I stop writing this stupid paragraph?\r\nSoon hopefully."; + + SHOW_SPLIT(split_2, 5, 20); + SHOW_SPLIT(split_2, 0, 30); + SHOW_SPLIT(split_2, 8, 11); + SHOW_SPLIT(split_2, 28, 41); + SHOW_SPLIT(split_2, 58, 79); + + astring split_3 = "This string exists for just one purpose; it will be showing how the thing handles a really long word at the end. And that word is... califragilisticexpialadociuosberriesinatreearerottingnowsomamacasscanyoupleasehelpme"; + + SHOW_SPLIT(split_3, 0, 5); + SHOW_SPLIT(split_3, 10, 30); + + astring split_4 = "This string\n\n\nhas multiple CRs gwan on.\r\n\r\nDoes this cause problems?\n\n\n\n"; + + SHOW_SPLIT(split_4, 3, 10); + SHOW_SPLIT(split_4, 8, 20); + + return final_report(); +} + +HOOPLE_MAIN(test_splitter, ); + diff --git a/nucleus/library/tests_textual/test_xml_generator.cpp b/nucleus/library/tests_textual/test_xml_generator.cpp new file mode 100644 index 00000000..923e438c --- /dev/null +++ b/nucleus/library/tests_textual/test_xml_generator.cpp @@ -0,0 +1,122 @@ +/* +* Name : test_xml_generator +* Author : Chris Koeritz +* Purpose: Checks out whether the XML writer seems to be functional. +** +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +//using namespace filesystem; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) + +class test_xml_generator : public virtual unit_base, virtual public application_shell +{ +public: + test_xml_generator() {} + DEFINE_CLASS_NAME("test_xml_generator"); + int execute(); +}; + +#define OPERATE_XML(func, args, test_name) { \ + outcome ret = ted.func args; \ + ASSERT_EQUAL(ret.value(), xml_generator::OKAY, \ + astring(test_name) + astring(": failed to ") + #func); \ +} + +int test_xml_generator::execute() +{ + FUNCDEF("execute"); + xml_generator ted; + #define TEST "boilerplate" + + string_table attribs; + attribs.add("bluebird", "petunia chowder"); + OPERATE_XML(add_header, ("glommage", attribs), TEST); + + OPERATE_XML(open_tag, ("Recipe"), TEST); + + OPERATE_XML(open_tag, ("Name"), TEST); + OPERATE_XML(add_content, ("Lime Jello Marshmallow Cottage Cheese Surprise"), + TEST); + OPERATE_XML(close_tag, ("Name"), TEST); + + OPERATE_XML(open_tag, ("Description"), TEST); + OPERATE_XML(add_content, ("My grandma's favorite (may she rest in peace)."), + TEST); + OPERATE_XML(close_tag, ("Description"), TEST); + + #undef TEST + #define TEST "stirring ingredients" + OPERATE_XML(open_tag, ("Ingredients"), TEST); + + ////////////// + + OPERATE_XML(open_tag, ("Ingredient"), TEST); + + attribs.reset(); + attribs.add("unit", "box"); + OPERATE_XML(open_tag, ("Qty", attribs), TEST); + OPERATE_XML(add_content, ("1"), TEST); + OPERATE_XML(close_tag, ("Qty"), TEST); + + OPERATE_XML(open_tag, ("Item"), TEST); + OPERATE_XML(add_content, ("lime gelatin"), TEST); + OPERATE_XML(close_tag, ("Item"), TEST); + + OPERATE_XML(close_tag, ("Ingredient"), TEST); + + ////////////// + + OPERATE_XML(open_tag, ("Ingredient"), TEST); + + attribs.reset(); + attribs.add("unit", "g"); + OPERATE_XML(open_tag, ("Qty", attribs), TEST); + OPERATE_XML(add_content, ("500"), TEST); + OPERATE_XML(close_tag, ("Qty"), TEST); + + OPERATE_XML(open_tag, ("Item"), TEST); + OPERATE_XML(add_content, ("multicolored tiny marshmallows"), TEST); + OPERATE_XML(close_tag, ("Item"), TEST); + + OPERATE_XML(close_tag, ("Ingredient"), TEST); + + ////////////// + + #undef TEST + #define TEST "closing the bowl" + + OPERATE_XML(close_tag, ("Ingredients"), TEST); + + astring generated = ted.generate(); + LOG(astring("XML generated is as follows:")); + LOG(generated); + + return final_report(); +} + +HOOPLE_MAIN(test_xml_generator, ) + diff --git a/nucleus/library/tests_timely/makefile b/nucleus/library/tests_timely/makefile new file mode 100644 index 00000000..3b91fe7b --- /dev/null +++ b/nucleus/library/tests_timely/makefile @@ -0,0 +1,12 @@ +include cpp/variables.def + +PROJECT = tests_timely +TYPE = test +TARGETS = test_earth_time.exe +DEFINITIONS += USE_HOOPLE_DLLS +LOCAL_LIBS_USED = unit_test application processes loggers configuration mathematics nodes \ + structures textual timely filesystem structures basis +RUN_TARGETS = $(ACTUAL_TARGETS) + +include cpp/rules.def + diff --git a/nucleus/library/tests_timely/test_earth_time.cpp b/nucleus/library/tests_timely/test_earth_time.cpp new file mode 100644 index 00000000..716a14b1 --- /dev/null +++ b/nucleus/library/tests_timely/test_earth_time.cpp @@ -0,0 +1,136 @@ +/* +* Name : test_earth_time * +* Author : Chris Koeritz * +** +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#define DEBUG_EARTH_TIME + // set this to enable debugging features of the string class. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace mathematics; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; +using namespace unit_test; + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) +#undef BASE_LOG +#define BASE_LOG(s) STAMPED_EMERGENCY_LOG(program_wide_logger::get(), s) + +const int TIME_FORMAT = clock_time::MERIDIAN | clock_time::SECONDS + | clock_time::MILLISECONDS; + // the way we like to see our seconds get printed out. + +////////////// + +class test_earth_time : virtual public unit_base, virtual public application_shell +{ +public: + test_earth_time() {} + DEFINE_CLASS_NAME("test_earth_time"); + + virtual int execute(); + + void run_test_01(); + void run_test_02(); +}; + +////////////// + +void test_earth_time::run_test_01() +{ + FUNCDEF("run_test_01"); + // this test makes sure that clock_time's normalize is working as expected. + + time_locus checker_1(clock_time(12, 0, 60), day_in_year(), 2007); + clock_time::normalize(checker_1); + time_locus compare_1(clock_time(12, 1, 0), day_in_year(), 2007); +//BASE_LOG(astring("a=") + checker_1.text_form(TIME_FORMAT)); +//BASE_LOG(astring("b=") + compare_1.text_form(TIME_FORMAT)); + ASSERT_EQUAL(checker_1, compare_1, "normalize should not fail test 1"); + + time_locus checker_2(clock_time(12, 0, -1), day_in_year(), 2007); + clock_time::normalize(checker_2); + time_locus compare_2(clock_time(11, 59, 59), day_in_year(), 2007); + ASSERT_EQUAL(checker_2, compare_2, "normalize should not fail test 2"); + + time_locus checker_3(clock_time(11, 59, 61), day_in_year(), 2007); + clock_time::normalize(checker_3); + time_locus compare_3(clock_time(12, 00, 01), day_in_year(), 2007); + ASSERT_EQUAL(checker_3, compare_3, "normalize should not fail test 3"); + + time_locus checker_4(clock_time(12, 54, -61), day_in_year(), 2007); + clock_time::normalize(checker_4); + time_locus compare_4(clock_time(12, 52, 59), day_in_year(), 2007); + ASSERT_EQUAL(checker_4, compare_4, "normalize should not fail test 4"); + + time_locus checker_5(clock_time(12, -32, -62), day_in_year(), 2007); + clock_time::normalize(checker_5); + time_locus compare_5(clock_time(11, 26, 58), day_in_year(), 2007); + ASSERT_EQUAL(checker_5, compare_5, "normalize should not fail test 5"); +} + +void test_earth_time::run_test_02() +{ + FUNCDEF("run_test_02"); + // this test makes sure that day_in_year's normalize is working as expected. + + time_locus checker_1(clock_time(0, 0, -1), day_in_year(JANUARY, 1), 2007); + time_locus::normalize(checker_1); + time_locus compare_1(clock_time(23, 59, 59), day_in_year(DECEMBER, 31), 2006); +//BASE_LOG(astring("a=") + checker_1.text_form(TIME_FORMAT)); +//BASE_LOG(astring("b=") + compare_1.text_form(TIME_FORMAT)); + ASSERT_EQUAL(checker_1, compare_1, "normalize should not fail test 1"); + + time_locus checker_2(clock_time(23, 59, 60), day_in_year(DECEMBER, 31), 2007); + time_locus::normalize(checker_2); + time_locus compare_2(clock_time(0, 0, 0), day_in_year(JANUARY, 1), 2008); + ASSERT_EQUAL(checker_2, compare_2, "normalize should not fail test 2"); + + +//add more cases! +// test leap years +// test lotso things. + +} + +int test_earth_time::execute() +{ + FUNCDEF("execute"); + + run_test_01(); + run_test_02(); + + return final_report(); +} + +////////////// + +HOOPLE_MAIN(test_earth_time, ) + diff --git a/nucleus/library/textual/byte_formatter.cpp b/nucleus/library/textual/byte_formatter.cpp new file mode 100644 index 00000000..0aa73932 --- /dev/null +++ b/nucleus/library/textual/byte_formatter.cpp @@ -0,0 +1,324 @@ +/*****************************************************************************\ +* * +* Name : byte_formatter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "byte_formatter.h" +#include "parser_bits.h" +#include "string_manipulation.h" + +#include +#include +#include + +//#define DEBUG_BYTE_FORMAT + // uncomment for noisier version. + +#undef LOG +#ifdef DEBUG_BYTE_FORMAT + #define LOG(s) printf("%s\n", astring(s).s()) +#else + #define LOG(s) {} +#endif + +#define LINE_SIZE 80 + +using namespace basis; +using namespace structures; + +namespace textual { + +void byte_formatter::print_char(abyte to_print, astring &out, char replace) +{ + int temp = to_print % 128; + if (!parser_bits::is_printable_ascii(to_print)) out += replace; + else out += char(temp); +} + +void byte_formatter::print_chars(const abyte *to_print, int len, astring &out, char replace) +{ + for (int i = 0; i < len; i++) + print_char(to_print[i], out, replace); +} + +void byte_formatter::make_eight(basis::un_int num, astring &out) +{ + basis::un_int thresh = 0x10000000; + while (thresh >= 0x10) { + if (num < thresh) + out += '0'; + thresh >>= 4; // zap a nibble. + } +} + +astring byte_formatter::text_dump(const abyte *location, basis::un_int length, basis::un_int label, + const char *eol) +{ + astring to_return; + text_dump(to_return, location, length, label, eol); + return to_return; +} + +void byte_formatter::text_dump(astring &output, const byte_array &to_dump, basis::un_int label, + const char *eol) +{ + text_dump(output, to_dump.observe(), to_dump.length(), label, eol); +} + +astring byte_formatter::text_dump(const byte_array &to_dump, basis::un_int label, const char *eol) +{ + astring output; + text_dump(output, to_dump.observe(), to_dump.length(), label, eol); + return output; +} + +// this is the real version of text_dump. all the others use it. +void byte_formatter::text_dump(astring &to_return, const abyte *location, basis::un_int length, + basis::un_int label, const char *eol) +{ + to_return = ""; + int entry_size = 4; + int preamble = 14; + + basis::un_int entries_per_line = (LINE_SIZE - preamble) / entry_size; + + for (basis::un_int i = 0; i < length; i += entries_per_line) { + make_eight(i + label, to_return); + to_return += astring(astring::SPRINTF, "%x", i + label) + astring(" | "); + for (basis::un_int j = 0; j < entries_per_line; j++) { + if (i + j >= length) { + // if at the end of the loop, just print spaces. + to_return += " "; + } else { + int ord_of_current_char = *(location + i + j) & 0xFF; + if (ord_of_current_char < 0x10) to_return += '0'; + to_return += astring(astring::SPRINTF, "%x", int(ord_of_current_char)); + to_return += ' '; + } + } + + to_return += "| "; + for (basis::un_int k = i; k < i + entries_per_line; k++) { + if (k >= length) to_return += ' '; + // if past the end of the block, just add spaces. + else print_char(*(location + k), to_return); + } + to_return += astring(" |") + eol; + } +} + +void byte_formatter::parse_dump(const astring &dumped_form, byte_array &bytes_found) +{ + bytes_found.reset(); + string_array lines_found; + // iterate over the string and break it up into lines. + for (int i = 0; i < dumped_form.length(); i++) { + int indy = dumped_form.find('\n', i); +//hmmm: not platform invariant. what about '\r' if we see it? + + if (negative(indy)) { + // no more lines found. + if (i < dumped_form.length() - 1) { + // grab the last bit as a line. + lines_found += dumped_form.substring(i, dumped_form.length() - 1); + } + break; + } + // found a normal line ending, so drop everything from the current + // position up to the ending into the list of strings. + lines_found += dumped_form.substring(i, indy - 1); + i = indy + 1; // jump to next potential line. + } + // now process the lines that we've found. + for (int j = 0; j < lines_found.length(); j++) { + // first step is to find the pipe character that brackets the actual + // data. we ignore the "address" located before the pipe. + astring &s = lines_found[j]; + int bar_one = s.find('|', 0); + if (negative(bar_one)) continue; // skip this one; it's malformed. + // now we look for the second pipe that comes before the text form of + // the data. we don't care about the text or anything after. + int bar_two = s.find('|', bar_one + 1); + if (negative(bar_two)) continue; // skip for same reason. + astring s2 = s.substring(bar_one + 1, bar_two - 1); + byte_array this_part; + string_to_bytes(s2, this_part); + bytes_found += this_part; + } +} + +////////////// + +void byte_formatter::bytes_to_string(const abyte *to_convert, int length, astring &as_string, + bool space_delimited) +{ + if (!to_convert || !length) return; // nothing to do. + if (negative(length)) return; // bunk. + as_string = ""; // reset the output parameter. + + // the pattern is used for printing the bytes and considering the delimiter. + astring pattern("%02x"); + if (space_delimited) pattern += " "; + + // now zip through the array and dump it into the string. + for (int i = 0; i < length; i++) + as_string += astring(astring::SPRINTF, pattern.s(), to_convert[i]); +} + +// returns true if the character is within the valid ranges of hexadecimal +// nibbles (as text). +bool byte_formatter::in_hex_range(char to_check) +//hmmm: move this to parser bits. +{ + return ( (to_check <= '9') && (to_check >= '0') ) + || ( (to_check <= 'f') && (to_check >= 'a') ) + || ( (to_check <= 'F') && (to_check >= 'A') ); +} + +void byte_formatter::string_to_bytes(const char *to_convert, byte_array &as_array) +{ + as_array.reset(); // clear the array. + const int len = int(strlen(to_convert)); + + // the parser uses a simple state machine for processing the string. + enum states { FINDING_HEX, IGNORING_JUNK }; + states state = IGNORING_JUNK; + + int digits = 0; // the number of digits we've currently found. + int accumulator = 0; // the current hex duo. + + // loop through the string. + for (int i = 0; i < len; i++) { + switch (state) { + case IGNORING_JUNK: { + if (in_hex_range(to_convert[i])) { + i--; // skip back to where we were before now. + state = FINDING_HEX; + continue; // jump to the other state. + } + // otherwise, we could care less what the character is. + break; + } + case FINDING_HEX: { + if (digits >= 2) { + // we have finished a hex byte. + as_array += abyte(accumulator); + accumulator = 0; + digits = 0; + i--; // skip back for the byte we haven't eaten yet. + state = IGNORING_JUNK; // jump to other state for a new item. + continue; + } + // we really think this is a digit here and we're not through with + // accumulating them. + accumulator <<= 4; + digits++; + accumulator += string_manipulation::char_to_hex(to_convert[i]); + + // now we sneakily check the next character. + if (!in_hex_range(to_convert[i+1])) { + // we now know we should not be in this state for long. + if (digits) { + // there's still some undigested stuff. + digits = 2; // fake a finished byte. + continue; // keep going, but eat the character we were at. + } + // well, there's nothing lost if we just jump to that state. + state = IGNORING_JUNK; + continue; + } + break; + } + } + } + if (digits) { + // snag the last unfinished bit. + as_array += abyte(accumulator); + } +} + +void byte_formatter::bytes_to_string(const byte_array &to_convert, astring &as_string, + bool space_delimited) +{ + bytes_to_string(to_convert.observe(), to_convert.length(), as_string, + space_delimited); +} + +void byte_formatter::string_to_bytes(const astring &to_convert, byte_array &as_array) +{ string_to_bytes(to_convert.s(), as_array); } + +void byte_formatter::bytes_to_shifted_string(const byte_array &to_convert, astring &as_string) +{ +#ifdef DEBUG_BYTE_FORMAT + FUNCDEF("bytes_to_shifted_string"); +#endif + bit_vector splitter(8 * to_convert.length(), to_convert.observe()); + int i; // track our current position. + for (i = 0; i < splitter.bits(); i += 7) { + abyte curr = 1; // start with a bit set already. + for (int j = i; j < i + 7; j++) { + curr <<= 1; // move to the left. + if (j < splitter.bits()) + curr |= abyte(splitter.on(j)); // or in the current position. + } + as_string += char(curr); + } +#ifdef DEBUG_BYTE_FORMAT + LOG(a_sprintf("%d bytes comes out as %d char string.", + to_convert.length(), as_string.length()).s()); +#endif +} + +void byte_formatter::shifted_string_to_bytes(const astring &to_convert, byte_array &as_array) +{ +#ifdef DEBUG_BYTE_FORMAT + FUNCDEF("shifted_string_to_bytes"); +#endif + bit_vector accumulator; + + for (int i = 0; i < to_convert.length(); i++) { + abyte current = abyte(to_convert[i]) & 0x7F; + // get the current bits but remove the faux sign bit. + accumulator.resize(accumulator.bits() + 7); + // now shift off the individual pieces. + for (int j = 0; j < 7; j++) { + // get current bit's value. + current <<= 1; // shift it up. + abyte set_here = current & 0x80; // test the highest order bit. + // now flip that bit on or off based on what we saw. + accumulator.set_bit(i * 7 + j, bool(set_here)); + } + } + + int remainder = accumulator.bits() % 8; + accumulator.resize(accumulator.bits() - remainder); + // chop off any extraneous bits that are due to our shifting. + +#ifdef DEBUG_BYTE_FORMAT + // there should be no remainder. and the number of bits should be a multiple + // of eight now. + if (accumulator.bits() % 8) + deadly_error("byte_formatter", func, "number of bits is erroneous."); +#endif + + const byte_array &accumref = accumulator; + for (int q = 0; q < accumulator.bits() / 8; q++) + as_array += accumref[q]; + +#ifdef DEBUG_BYTE_FORMAT + LOG(a_sprintf("%d chars comes out as %d bytes.", + to_convert.length(), as_array.length()).s()); +#endif +} + +} // namespace + diff --git a/nucleus/library/textual/byte_formatter.h b/nucleus/library/textual/byte_formatter.h new file mode 100644 index 00000000..b67f997e --- /dev/null +++ b/nucleus/library/textual/byte_formatter.h @@ -0,0 +1,122 @@ +#ifndef BYTE_FORMATTER_CLASS +#define BYTE_FORMATTER_CLASS + +/*****************************************************************************\ +* * +* Name : byte_formatter * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1992-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +namespace textual { + +//! Provides functions for manipulating arrays of bytes. + +class byte_formatter : public virtual basis::root_object +{ +public: + virtual ~byte_formatter() {} + + DEFINE_CLASS_NAME("byte_formatter"); + + static void print_char(basis::abyte to_print, basis::astring &out, + char replace = '_'); + //!< prints the byte "to_print" into "out" as long as "to_print" is readable. + /*!< if it's not readable, then the "replace" is printed. */ + + static void print_chars(const basis::abyte *to_print, int length, + basis::astring &out, char replace = '_'); + //!< sends the bytes in "to_print" of "length" bytes into the string "out". + + static void text_dump(basis::astring &output, const basis::abyte *location, + basis::un_int length, basis::un_int label = 0, const char *eol = "\n"); + //!< prints out a block of memory in a human readable form. + /*!< it is stored in the "output" string. the "location" is where to dump, + the "length" is the number of bytes to dump, and the "label" is where to + start numbering the location label on the first line. the "eol" supplies + the line ending sequence to be used for the output file. this should be + "\r\n" for win32. */ + + static basis::astring text_dump(const basis::abyte *location, basis::un_int length, + basis::un_int label = 0, const char *eol = "\n"); + //!< this is a less efficient version of text_dump that returns a string. + /*!< it's easier to use when combining astrings. */ + + static void text_dump(basis::astring &output, + const basis::byte_array &to_dump, basis::un_int label = 0, const char *eol = "\n"); + //!< a version that operates on a byte_array and stores into a string. + static basis::astring text_dump(const basis::byte_array &to_dump, + basis::un_int label = 0, const char *eol = "\n"); + //!< a version that operates on a byte_array and returns a string. + + //! this operation performs the inverse of a text_dump. + static void parse_dump(const basis::astring &dumped_form, + basis::byte_array &bytes_found); + +////////////// + + static void bytes_to_string(const basis::byte_array &to_convert, + basis::astring &as_string, bool space_delimited = true); + //!< converts a byte_array into a string. + /*!< takes an array of bytes "to_convert" and spits out the equivalent form + "as_string". if "space_delimited" is true, then the bytes are separated + by spaces. */ + + static void string_to_bytes(const basis::astring &to_convert, + basis::byte_array &as_array); + //!< wrangles the string "to_convert" into an equivalent byte form "as_array". + /*!< this is a fairly forgiving conversion; it will accept any string and + strip out the hexadecimal bytes. spacing is optional, but every two + hex nibbles together will be taken as a byte. if there are an odd number + of nibbles, then the odd one will be taken as the least significant half + of a byte. */ + + static void bytes_to_string(const basis::abyte *to_convert, int length, + basis::astring &as_string, bool space_delimited = true); + //!< a version that accepts a byte pointer and length, rather than byte_array. + + static void string_to_bytes(const char *to_convert, + basis::byte_array &as_array); + //!< a version that works with the char pointer rather than an astring. + +////////////// + + static void bytes_to_shifted_string + (const basis::byte_array &to_convert, basis::astring &as_string); + //!< this is a special purpose converter from bytes to character strings. + /*!< it merely ensures that the "as_string" version has no zero bytes + besides the end of string null byte. this packs 7 bits of data into each + character, resulting in an 87.5% efficient string packing of the array. + the resulting string is not readable. the "as_string" parameter is not + reset; any data will be appended to it. */ + + static void shifted_string_to_bytes(const basis::astring &to_convert, + basis::byte_array &as_array); + //!< unshifts a string "to_convert" back into a byte_array. + /*!< converts a string "to_convert" created by bytes_to_shifted_string() into + the original array of bytes and stores it in "as_array". the "as_array" + parameter is not reset; any data will be appended to it. */ + +////////////// + + // utility methods to help building the formatted strings. + + static void make_eight(basis::un_int num, basis::astring &out); + + static bool in_hex_range(char to_check); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/textual/list_parsing.cpp b/nucleus/library/textual/list_parsing.cpp new file mode 100644 index 00000000..9cac4a8f --- /dev/null +++ b/nucleus/library/textual/list_parsing.cpp @@ -0,0 +1,279 @@ +/*****************************************************************************\ +* * +* Name : list_parsing * +* Author : Chris Koeritz * +* Author : Gary Hardley * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "list_parsing.h" +#include "parser_bits.h" + +#include +#include +#include + +#include +#include + +using namespace basis; +using namespace structures; + +namespace textual { + +#undef LOG +#define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s()) + +list_parsing::~list_parsing() {} // needed since we use the class_name macro. + +// by Gary Hardley. +bool list_parsing::get_ids_from_string(const astring &to_parse, int_set &identifiers) +{ + identifiers.clear(); // clear existing ids, if any. + int_array found; + bool ret = get_ids_from_string(to_parse, found); + if (!ret) return false; + for (int i = 0; i < found.length(); i++) identifiers.add(found[i]); + return true; +} + +// by Gary Hardley. +bool list_parsing::get_ids_from_string(const astring &to_parse, + int_array &identifiers) +{ + identifiers.reset(); // clear existing ids, if any. + if (!to_parse) return false; + // if an empty string is passed, return an empty set. + + int last_id = -1; + int tmp_id; + bool done = false; + char last_separator = ' '; + + int index = 0; + while (!done && (index < to_parse.length())) { + tmp_id = 0; + bool got_digit = false; + while ( (to_parse[index] != ',') && (to_parse[index] != '-') + && (to_parse[index] != ' ') && (index < to_parse.length()) ) { + if (!isdigit(to_parse[index])) return false; + tmp_id *= 10; + tmp_id += int(to_parse[index++]) - 0x30; + got_digit = true; + } + + if (got_digit) { + if (tmp_id > MAXINT32) return false; + + if (last_id == -1) { + last_id = tmp_id; + identifiers += last_id; + } else { + // if the last separator was a dash, this is a range + if (last_separator == '-') { + if (tmp_id >= last_id) { + for (int i = last_id + 1; i <= tmp_id; i++) + identifiers += i; + } + else { + for (int i = tmp_id; i < last_id; i++) + identifiers += i; + } + last_id = 0; + last_separator = ' '; + } else { + last_id = tmp_id; + identifiers += last_id; + } + } + } else { + // did not read an address, to_parse[index] must be a non-digit. + if ( (to_parse[index] != ' ') && (to_parse[index] != '-') + && (to_parse[index] != ',') ) return false; + last_separator = to_parse[index++]; + } + } + return true; +} + +//by chris koeritz. +astring list_parsing::put_ids_in_string(const int_set &ids, char separator) +{ + astring to_return; + for (int i = 0; i < ids.length(); i++) { + to_return += a_sprintf("%d", ids[i]); + if (i < ids.length() - 1) { + to_return += separator; + to_return += " "; + } + } + return to_return; +} + +//by chris koeritz. +astring list_parsing::put_ids_in_string(const int_array &ids, char separator) +{ + astring to_return; + for (int i = 0; i < ids.length(); i++) { + to_return += a_sprintf("%d", ids[i]); + if (i < ids.length() - 1) { + to_return += separator; + to_return += " "; + } + } + return to_return; +} + +// ensures that quotes inside the string "to_emit" are escaped. +astring list_parsing::emit_quoted_chunk(const astring &to_emit) +{ + astring to_return('\0', 256); // avoid reallocations with large pre-alloc. + to_return = ""; // reset to get blank string but keep pre-alloc. + for (int i = 0; i < to_emit.length(); i++) { + char next_char = to_emit[i]; + if ( (next_char == '"') || (next_char == '\\') ) + to_return += "\\"; // add the escape before quote or backslash. + to_return += astring(next_char, 1); + } + return to_return; +} + +void list_parsing::create_csv_line(const string_table &to_csv, astring &target) +{ + target = astring::empty_string(); + for (int i = 0; i < to_csv.symbols(); i++) { + target += astring("\"") + emit_quoted_chunk(to_csv.name(i)) + + "=" + emit_quoted_chunk(to_csv[i]) + "\""; + if (i < to_csv.symbols() - 1) target += ","; + } +} + +void list_parsing::create_csv_line(const string_array &to_csv, astring &target) +{ + target = astring::empty_string(); + for (int i = 0; i < to_csv.length(); i++) { + target += astring("\"") + emit_quoted_chunk(to_csv[i]) + "\""; + if (i < to_csv.length() - 1) target += ","; + } +} + +// we do handle escaped quotes for csv parsing, so check for backslash. +// and since we handle escaped quotes, we also have to handle escaping the +// backslash (otherwise a quoted item with a backslash as the last character +// cannot be handled appropriately, because it will be interpreted as an +// escaped quote instead). no other escapes are implemented right now. +#define handle_escapes \ + if (to_parse[i] == '\\') { \ + if ( (to_parse[i + 1] == '"') || (to_parse[i + 1] == '\\') ) { \ + i++; \ + accumulator += to_parse[i]; \ + continue; /* skip normal handling in sequel. */ \ + } \ + } + +const int ARRAY_PREFILL_AMOUNT = 7; + // a random default for pre-filling. + +#define ADD_LINE_TO_FIELDS(new_line) { \ + storage_slot++; /* move to next place to store item. */ \ + /* make sure we have enough space for the next slot and then some. */ \ +/*LOG(a_sprintf("fields curr=%d stowslot=%d", fields.length(), storage_slot));*/ \ + if (fields.length() < storage_slot + 2) \ + fields.insert(fields.length(), ARRAY_PREFILL_AMOUNT); \ +/*LOG(a_sprintf("now fields=%d stowslot=%d", fields.length(), storage_slot));*/ \ + fields[storage_slot] = new_line; \ +} + +//hmmm: parameterize what is meant by a quote. maybe comma too. +//by chris koeritz. +bool list_parsing::parse_csv_line(const astring &to_parse, string_array &fields) +{ + FUNCDEF("parse_csv_line"); + // the current field we're chowing. we puff it out to start with to + // avoid paying for expanding its memory later. + astring accumulator(' ', 256); + accumulator = astring::empty_string(); + + // the state machine goes through these states until the entire string + // is consumed. + enum states { seeking_quote, eating_string, seeking_comma }; + states state = seeking_quote; + + bool no_second_quote = false; // true if we started without a quote. + bool just_saw_comma = false; // true if seeking comma was the last state. + + int storage_slot = -1; + + for (int i = 0; i < to_parse.length(); i++) { + switch (state) { + case seeking_quote: + if (parser_bits::white_space(to_parse[i])) continue; + if (to_parse[i] == ',') { + // a missing quoted string counts as an empty string. + ADD_LINE_TO_FIELDS(astring::empty_string()); + just_saw_comma = true; + continue; + } + just_saw_comma = false; // cancel that state. + if (to_parse[i] != '"') { + // short circuit the need for a quote. + accumulator += to_parse[i]; + no_second_quote = true; + } + state = eating_string; + break; + case eating_string: + just_saw_comma = false; // no longer true. + if (no_second_quote && (to_parse[i] != ',') ) { + handle_escapes; + accumulator += to_parse[i]; + } else if (!no_second_quote && (to_parse[i] != '"') ) { + handle_escapes; + accumulator += to_parse[i]; + } else { + // we found the closing quote (or comma). add the string. + if (no_second_quote) { + state = seeking_quote; + just_saw_comma = true; + } else state = seeking_comma; + ADD_LINE_TO_FIELDS(accumulator) + accumulator = astring::empty_string(); + no_second_quote = false; + } + break; + case seeking_comma: + if (parser_bits::white_space(to_parse[i])) continue; + if (to_parse[i] == ',') { + // we got what we wanted. + state = seeking_quote; + just_saw_comma = true; + continue; + } + // well, there was no comma. that's an error. + return false; + break; + default: { + LOG("erroneous state reached during csv parsing"); + break; + } + } + } + if ( (state == eating_string) && (accumulator.length()) ) + ADD_LINE_TO_FIELDS(accumulator) + else if (just_saw_comma) + ADD_LINE_TO_FIELDS(astring::empty_string()) + if (fields.length() > storage_slot + 1) + fields.zap(storage_slot + 1, fields.last()); + return true; +} + +} //namespace. + + diff --git a/nucleus/library/textual/list_parsing.h b/nucleus/library/textual/list_parsing.h new file mode 100644 index 00000000..0a2c9292 --- /dev/null +++ b/nucleus/library/textual/list_parsing.h @@ -0,0 +1,72 @@ +#ifndef LIST_PARSING_CLASS +#define LIST_PARSING_CLASS + +/*****************************************************************************\ +* * +* Name : list_parsing * +* Author : Chris Koeritz * +* Author : Gary Hardley * +* * +******************************************************************************* +* Copyright (c) 2002-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +namespace textual { + +//! A set of functions that help out with parsing lists of things. + +class list_parsing +{ +public: + virtual ~list_parsing(); + DEFINE_CLASS_NAME("list_parsing"); + + static bool get_ids_from_string(const basis::astring &string, structures::int_set &ids); + //!< returns true if a set of unique ids can be extracted from "string". + /*!< valid separators are spaces, commas, hyphens. note that this + returns an int_set although the integers will all be non-negative. + e.g. "1-4,5 6 7,30-25" is a valid string. */ + + static bool get_ids_from_string(const basis::astring &string, basis::int_array &ids); + //!< same as above except result is an array -- to preserve order. + /*!< this also retains duplicates. */ + + static basis::astring put_ids_in_string(const structures::int_set &ids, char separator = ','); + //!< returns a string containing a "separator" separated list of ids. + /*!< spaces are also used between entries for readability. */ + static basis::astring put_ids_in_string(const basis::int_array &ids, char separator = ','); + //!< operates on an array instead of a set. + + static basis::astring emit_quoted_chunk(const basis::astring &to_emit); + //!< ensures that quotes inside the string "to_emit" are escaped. + + static bool parse_csv_line(const basis::astring &to_parse, structures::string_array &fields); + //!< examines the string "to_parse" which should be in csv format. + /*!< the "fields" list is set to the entries found on the line. true is + returned if the line parsed without any errors. this method will accept + a backslash as an escape character if it is immediately followed by a + quote character or another backslash character. no other escapes are + currently supported; backslashes will be taken literally otherwise. */ + + static void create_csv_line(const structures::string_array &to_csv, basis::astring &target); + static void create_csv_line(const structures::string_table &to_csv, basis::astring &target); + //!< writes a CSV line of text into the "target" from the items in "to_csv". + /*!< the "target" is reset before the line is stored there; thus, this is + not cumulative. further, the end of line character is not appended. this + will escape quote and backslash characters with a prepended backslash. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/textual/makefile b/nucleus/library/textual/makefile new file mode 100644 index 00000000..3b7d9199 --- /dev/null +++ b/nucleus/library/textual/makefile @@ -0,0 +1,10 @@ +include cpp/variables.def + +PROJECT = textual +TYPE = library +SOURCE = byte_formatter.cpp list_parsing.cpp parser_bits.cpp string_manipulation.cpp \ + xml_generator.cpp xml_parser.cpp +TARGETS = textual.lib + +include cpp/rules.def + diff --git a/nucleus/library/textual/parser_bits.cpp b/nucleus/library/textual/parser_bits.cpp new file mode 100644 index 00000000..0bf33179 --- /dev/null +++ b/nucleus/library/textual/parser_bits.cpp @@ -0,0 +1,223 @@ +/*****************************************************************************\ +* * +* Name : parser_bits * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "parser_bits.h" + +#include +#include +#include + +#include +#include + +using namespace basis; + +#undef LOG +#define LOG(prf) printf("%s\n", basis::astring(prf).s()) + +namespace textual { + +parser_bits::line_ending parser_bits::platform_eol() +{ +#ifdef __UNIX__ + // obviously a unix OS, unless someone's playing games with us. + return LF_AT_END; +#elif defined(__WIN32__) + // smells like DOS. + return CRLF_AT_END; +#else + // pick the unix default if we can't tell. + return LF_AT_END; +#endif +} + +const char *parser_bits::eol_to_chars(line_ending end) +{ + static const char *CRLF_AT_END_STRING = "\r\n"; + static const char *LF_AT_END_STRING = "\n"; + static const char *NO_ENDING_STRING = ""; + + switch (end) { + case CRLF_AT_END: return CRLF_AT_END_STRING; + case NO_ENDING: return NO_ENDING_STRING; + case LF_AT_END: // fall-through to default. + default: return LF_AT_END_STRING; + } +} + +const char *parser_bits::platform_eol_to_chars() +{ return eol_to_chars(platform_eol()); } + +bool parser_bits::is_printable_ascii(char to_check) +{ return (to_check >= 32) && (to_check <= 126); } + +bool parser_bits::white_space_no_cr(char to_check) +{ return (to_check == ' ') || (to_check == '\t'); } + +bool parser_bits::is_eol(char to_check) +{ return (to_check == '\n') || (to_check == '\r'); } + +bool parser_bits::white_space(char to_check) +{ return white_space_no_cr(to_check) || is_eol(to_check); } + +void parser_bits::translate_CR_for_platform(astring &to_translate) +{ + line_ending plat_eol = platform_eol(); + bool last_was_lf = false; + for (int i = 0; i <= to_translate.end(); i++) { + if (to_translate[i] == '\r') { + if (last_was_lf) continue; // ignore two in a row. + last_was_lf = true; + } else if (to_translate[i] == '\n') { + if (last_was_lf) { + if (plat_eol != CRLF_AT_END) { + // fix it, since there was not supposed to be an LF. + to_translate.zap(i - 1, i - 1); + i--; + } + } else { + if (plat_eol == CRLF_AT_END) { + // fix it, since we're missing an LF that we want. + to_translate.insert(i, "\r"); + i++; + } + } + last_was_lf = false; + } else { + // not the two power characters. + last_was_lf = false; + } + } +} + +bool parser_bits::is_hexadecimal(char look_at) +{ + return range_check(look_at, 'a', 'f') + || range_check(look_at, 'A', 'F') + || range_check(look_at, '0', '9'); +} + +bool parser_bits::is_hexadecimal(const char *look_at, int len) +{ + for (int i = 0; i < len; i++) + if (!is_hexadecimal(look_at[i])) return false; + return true; +} + +bool parser_bits::is_hexadecimal(const astring &look_at, int len) +{ return is_hexadecimal(look_at.observe(), len); } + +bool parser_bits::is_alphanumeric(char look_at) +{ + return range_check(look_at, 'a', 'z') + || range_check(look_at, 'A', 'Z') + || range_check(look_at, '0', '9'); +} + +bool parser_bits::is_alphanumeric(const char *look_at, int len) +{ + for (int i = 0; i < len; i++) + if (!is_alphanumeric(look_at[i])) return false; + return true; +} + +bool parser_bits::is_alphanumeric(const astring &look_at, int len) +{ return is_alphanumeric(look_at.observe(), len); } + +bool parser_bits::is_identifier(char look_at) +{ + return range_check(look_at, 'a', 'z') + || range_check(look_at, 'A', 'Z') + || range_check(look_at, '0', '9') + || (look_at == '_'); +} + +bool parser_bits::is_identifier(const char *look_at, int len) +{ + if (is_numeric(look_at[0])) return false; + for (int i = 0; i < len; i++) + if (!is_identifier(look_at[i])) return false; + return true; +} + +bool parser_bits::is_identifier(const astring &look_at, int len) +{ return is_identifier(look_at.observe(), len); } + +bool parser_bits::is_numeric(char look_at) +{ + return range_check(look_at, '0', '9') || (look_at == '-'); +} + +bool parser_bits::is_numeric(const char *look_at, int len) +{ + for (int i = 0; i < len; i++) { + if (!is_numeric(look_at[i])) return false; + if ( (i > 0) && (look_at[i] == '-') ) return false; + } + return true; +} + +bool parser_bits::is_numeric(const astring &look_at, int len) +{ return is_numeric(look_at.observe(), len); } + +astring parser_bits::substitute_env_vars(const astring &to_process, + bool leave_unknown) +{ + astring editing = to_process; + +//LOG(astring("input to subst env: ") + to_process); + + int indy; // index of the dollar sign in the string. + while (true) { + indy = editing.find('$'); + if (negative(indy)) break; // all done. + int q; + for (q = indy + 1; q < editing.length(); q++) { + if (!parser_bits::is_identifier(editing[q])) + break; // done getting variable name. + } + if (q != indy + 1) { + // we caught something in our environment variable trap... + astring var_name = editing.substring(indy + 1, q - 1); +//LOG(astring("var name ") + var_name); + astring value_found = environment::get(var_name); +//LOG(astring("val found ") + value_found); + if (value_found.t()) { + editing.zap(indy, q - 1); + editing.insert(indy, value_found); + } else { + if (leave_unknown) { + // that lookup failed. let's mark it. + editing[indy] = '?'; + // simple replacement, shows variables that failed. + } else { + // replace it with blankness. + editing.zap(indy, q - 1); + } + } + } else { + // well, we didn't see a valid variable name, but we don't want to leave + // the dollar sign in there. + editing[indy] = '!'; // simple replacement, marks where syntax is bad. + } + + } + +//LOG(astring("output from subst env: ") + editing); + + return editing; +} + +} //namespace. + diff --git a/nucleus/library/textual/parser_bits.h b/nucleus/library/textual/parser_bits.h new file mode 100644 index 00000000..6ca2a21d --- /dev/null +++ b/nucleus/library/textual/parser_bits.h @@ -0,0 +1,124 @@ +#ifndef PARSER_BITS_CLASS +#define PARSER_BITS_CLASS + +/*****************************************************************************\ +* * +* Name : parser_bits * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace textual { + +//! Warehouses some functions that are often useful during text parsing. + +class parser_bits +{ +public: + //! Line endings is an enumeration of the separator character(s) used for text files. + /*! on unix, every line in a text file has a line feed (LF) character appended to the line. + on ms-dos and ms-windows, each line has a carriage return (CR) and line feed (LF) appended + instead. a synonym for the line_ending is "eol" which stands for "end of line". */ + enum line_ending { + LF_AT_END = -15, //!< Unix standard is LF_AT_END ("\n"). + CRLF_AT_END, //!< DOS standard is CRLF_AT_END ("\r\n"). + NO_ENDING //!< No additional characters added as line endings. + }; + + static const char *eol_to_chars(line_ending ending); + //!< returns the C string form for the "ending" value. + + static line_ending platform_eol(); + //!< provides the appropriate ending on the current OS platform. + + static const char *platform_eol_to_chars(); + //!< provides the characters that make up this platform's line ending. + + static void translate_CR_for_platform(basis::astring &to_translate); + //!< flips embedded EOL characters for this platform's needs. + /*!< runs through the string "to_translate" and changes any CR or CRLF + combinations into the EOL (end-of-line) character that's appropriate + for this operating system. */ + + static basis::astring substitute_env_vars(const basis::astring &text, + bool leave_unknown = true); + //!< resolves embedded environment variables in "text". + /*!< replaces the names of any environment variables in "text" with the + variable's value and returns the resulting string. the variable names + are marked by a single dollar before an alphanumeric identifier + (underscores are valid), for example: $PATH if the "leave_unknown" flag + is true, then any unmatched variables are left in the text with a question + mark instead of a dollar sign. if it's false, then they are simply + replaced with nothing at all. */ + + static bool is_printable_ascii(char to_check); + //!< returns true if "to_check" is a normally visible ASCII character. + /*!< this is defined very simply by it being within the range of 32 to + 126. that entire range should be printable in ASCII. before 32 we have + control characters. after 126 we have potentially freakish looking + characters. this is obviously not appropriate for utf-8 or unicode. */ + + static bool white_space_no_cr(char to_check); + //!< reports if "to_check" is white space but not a carriage return. + /*!< returns true if the character "to_check" is considered a white space, + but is not part of an end of line combo (both '\n' and '\r' are + disallowed). the allowed set includes tab ('\t') and space (' ') only. */ + + static bool is_eol(char to_check); + //!< returns true if "to_check" is part of an end-of-line sequence. + /*!< this returns true for both the '\r' and '\n' characters. */ + + static bool white_space(char to_check); + //!< returns true if the character "to_check" is considered a white space. + /*!< this set includes tab ('\t'), space (' '), carriage return ('\n'), + and line feed ('\r'). */ + + static bool is_alphanumeric(char look_at); + //!< returns true if "look_at" is one of the alphanumeric characters. + /*!< This includes a to z in either case and 0 to 9. */ + static bool is_alphanumeric(const char *look_at, int len); + //!< returns true if the char ptr "look_at" is all alphanumeric characters. + static bool is_alphanumeric(const basis::astring &look_at, int len); + //!< returns true if the string "look_at" is all alphanumeric characters. + + static bool is_numeric(char look_at); + //!< returns true if "look_at" is a valid numerical character. + /*! this allows the '-' character for negative numbers also (but only for + first character if the char* or astring versions are used). does not + support floating point numbers or exponential notation yet. */ + static bool is_numeric(const char *look_at, int len); + //!< returns true if "look_at" is all valid numerical characters. + static bool is_numeric(const basis::astring &look_at, int len); + //!< returns true if the "look_at" string has only valid numerical chars. + + static bool is_hexadecimal(char look_at); + //!< returns true if "look_at" is one of the hexadecimal characters. + /*!< This includes a to f in either case and 0 to 9. */ + static bool is_hexadecimal(const char *look_at, int len); + //!< returns true if "look_at" is all hexadecimal characters. + static bool is_hexadecimal(const basis::astring &look_at, int len); + //!< returns true if the string "look_at" is all hexadecimal characters. + + static bool is_identifier(char look_at); + //!< returns true if "look_at" is a valid identifier character. + /*!< this just allows alphanumeric characters and underscore. */ + static bool is_identifier(const char *look_at, int len); + //!< returns true if "look_at" is composed of valid identifier character. + /*!< additionally, identifiers cannot start with a number. */ + static bool is_identifier(const basis::astring &look_at, int len); + //!< like is_identifier() above but operates on a string. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/textual/string_convert.h b/nucleus/library/textual/string_convert.h new file mode 100644 index 00000000..697439e7 --- /dev/null +++ b/nucleus/library/textual/string_convert.h @@ -0,0 +1,70 @@ +#ifndef STRING_CONVERSION_GROUP +#define STRING_CONVERSION_GROUP + +/*****************************************************************************\ +* * +* Name : string_convert * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include + +#ifdef __WIN32__ + #ifndef _MANAGED + #ifndef __MINGW32__ + #define _WINSOCKAPI_ // the dance of the windows headers. + #include + #include + #endif + #endif +#endif + +// forward. +class _bstr_t; // ATL (Active Template Library) string type. + +//! A collection of conversions between popular string types. + +namespace string_convert +{ + +#ifdef _AFXDLL + //! conversion from MFC CString to astring. + inline astring to_astring(const CString &original) + { return astring(from_unicode_temp(original)); } + + //! conversion from astring to MFC CString. + inline CString to_CString(const astring &original) + { return CString(to_unicode_temp(original)); } +#endif + +#ifdef WIN32 + #ifndef _MANAGED + #ifndef __MINGW32__ + //! conversion from ATL's _bstr_t object to astring. + inline basis::astring to_astring(const _bstr_t &original) { + return basis::astring(basis::astring::UNTERMINATED, (const char *)original, + original.length()); + } + + //! conversion from astring to the ATL _bstr_t object. + inline _bstr_t to_bstr_t(const basis::astring &original) + { return _bstr_t(original.s()); } + #endif + #endif +#endif + +//other conversions. + +} //namespace + +#endif + diff --git a/nucleus/library/textual/string_manipulation.cpp b/nucleus/library/textual/string_manipulation.cpp new file mode 100644 index 00000000..6e21691a --- /dev/null +++ b/nucleus/library/textual/string_manipulation.cpp @@ -0,0 +1,351 @@ +/*****************************************************************************\ +* * +* Name : string_manipulation * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "parser_bits.h" +#include "string_manipulation.h" + +#include +#include +#include +#include + +using namespace basis; +using namespace mathematics; + +namespace textual { + +//SAFE_STATIC_CONST(astring_object, string_manipulation::splitter_finding_set, +// ("\t\r\n -,;?!.:")) +const char *splitter_finding_set = "\t\r\n -,;?!.:"; + // any of these characters make a valid place to break a line. + +astring string_manipulation::make_random_name(int min, int max) +{ + chaos rando; + int length = rando.inclusive(min, max); + // pick a size for the string. + astring to_return; + for (int i = 0; i < length; i++) { + int chah = rando.inclusive(0, 26); + // use a range one larger than alphabet size. + char to_add = 'a' + chah; + if (chah == 26) to_add = '_'; + // patch the extra value to be a separator. + to_return += to_add; + } + return to_return; +} + +astring string_manipulation::long_line(char line_item, int repeat) +{ return astring(line_item, repeat); } + +astring string_manipulation::indentation(int spaces) +{ + astring s; + for (int i = 0; i < spaces; i++) s += ' '; + return s; +} + +void string_manipulation::carriage_returns_to_spaces(astring &to_strip) +{ + for (int j = 0; j < to_strip.length(); j++) { + int original_j = j; // track where we started looking. + if (!parser_bits::is_eol(to_strip[j])) continue; + // we have found at least one CR. let's see what else there is. + if ( (to_strip[j] == '\r') && (to_strip[j + 1] == '\n') ) { + // this is looking like a DOS CR. let's skip that now. + j++; + } + j++; // skip the one we know is a CR. + if (parser_bits::is_eol(to_strip[j])) { + // we are seeing more than one carriage return in a row. let's + // truncate that down to just one. + j++; + while (parser_bits::is_eol(to_strip[j]) && (j < to_strip.length())) + j++; // skip to next one that might not be CR. + // now we think we know where there's this huge line of CRs. we will + // turn them all into spaces except the first. + to_strip[original_j] = '\n'; + for (int k = original_j + 1; k < j; k++) to_strip[k] = ' '; + // put the index back so we'll start looking at the non-CR char. + j--; + continue; // now skip back out to the main loop. + } else { + // we see only one carriage return, which we will drop in favor of + // joining those lines together. we iterate here since we might have + // seen a DOS CR taking up two spaces. + for (int k = original_j; k < j; k++) to_strip[k] = ' '; + } + } + +} + +void string_manipulation::split_lines(const astring &input_in, astring &output, + int min_column, int max_column) +{ + output = ""; + if (max_column - min_column + 1 < 2) return; // what's the point? + + astring input = input_in; // make a copy to work on. + carriage_returns_to_spaces(input); + + int col = min_column; + astring indent_add = indentation(min_column); + output = indent_add; // start with the extra space. + + bool just_had_break = false; + // set true if we just handled a line break in the previous loop. + bool put_accum_before_break = false; // true if we must postpone CR. + astring accumulated; + // holds stuff to print on next go-round. + + // now we parse across the list counting up our line size and making sure + // we don't go over it. + for (int j = 0; j < input.length(); j++) { + +//char to_print = input[j]; +//if (parser_bits::is_eol(to_print)) to_print = '_'; +//printf("[%d: val=%d, '%c', col=%d]\n", j, to_print, to_print, col); +//fflush(0); + + // handle the carriage return if it was ordered. + if (just_had_break) { + if (put_accum_before_break) { + output += accumulated; + // strip off any spaces from the end of the line. + output.strip_spaces(astring::FROM_END); + output += parser_bits::platform_eol_to_chars(); + accumulated = ""; + j++; // skip the CR that we think is there. + } + // strip off any spaces from the end of the line. + output.strip_spaces(astring::FROM_END); + output += parser_bits::platform_eol_to_chars(); + col = min_column; + output += indent_add; + just_had_break = false; + if (accumulated.length()) { + output += accumulated; + col += accumulated.length(); + accumulated = ""; + } + j--; + continue; + } + + put_accum_before_break = false; + + // skip any spaces we've got at the current position. + while ( (input[j] == ' ') || (input[j] == '\t') ) { + j++; + if (j >= input.length()) break; // break out of subloop if past it. + } + + if (j >= input.length()) break; // we're past the end. + + // handle carriage returns when they're at the current position. + char current_char = input[j]; + if (parser_bits::is_eol(current_char)) { + just_had_break = true; // set the state. + put_accum_before_break = true; + continue; + } + +//hmmm: the portion below could be called a find word break function. + + bool add_dash = false; // true if we need to break a word and add hyphen. + bool break_line = false; // true if we need to go to the next line. + bool invisible = false; // true if invisible characters were seen. + bool end_sentence = false; // true if there was a sentence terminator. + bool punctuate = false; // true if there was normal punctuation. + bool keep_on_line = false; // true if we want add current then break line. + char prior_break = '\0'; // set for real below. + char prior_break_plus_1 = '\0'; // ditto. + + // find where our next normal word break is, if possible. + int next_break = input.find_any(splitter_finding_set, j); + // if we didn't find a separator, just use the end of the string. + if (negative(next_break)) + next_break = input.length() - 1; + + // now we know where we're supposed to break, but we don't know if it + // will all fit. + prior_break = input[next_break]; + // hang onto the value before we change next_break. + prior_break_plus_1 = input[next_break + 1]; + // should still be safe since we're stopping before the last zero. + switch (prior_break) { + case '\r': case '\n': + break_line = true; + just_had_break = true; + put_accum_before_break = true; + // intentional fall-through. + case '\t': case ' ': + invisible = true; + next_break--; // don't include it in what's printed. + break; + case '?': case '!': case '.': + end_sentence = true; + // if we see multiples of these, we count them as just one. + while ( (input[next_break + 1] == '?') + || (input[next_break + 1] == '!') + || (input[next_break + 1] == '.') ) { + next_break++; + } + // make sure that there's a blank area after the supposed punctuation. + if (!parser_bits::white_space(input[next_break + 1])) + end_sentence = false; + break; + case ',': case ';': case ':': + punctuate = true; + // make sure that there's a blank area after the supposed punctuation. + if (!parser_bits::white_space(input[next_break + 1])) + punctuate = false; + break; + } + + // we'll need to add some spaces for certain punctuation. + int punct_adder = 0; + if (punctuate || invisible) punct_adder = 1; + if (end_sentence) punct_adder = 2; + + // check that we're still in bounds. + int chars_added = next_break - j + 1; + if (col + chars_added + punct_adder > max_column + 1) { + // we need to break before the next breakable character. + break_line = true; + just_had_break = true; + if (col + chars_added <= max_column + 1) { + // it will fit without the punctuation spaces, which is fine since + // it should be the end of the line. + invisible = false; + punctuate = false; + end_sentence = false; + punct_adder = 0; + keep_on_line = true; + } else if (min_column + chars_added > max_column + 1) { + // this word won't ever fit unless we break it. + int chars_left = max_column - col + 1; + // remember to take out room for the dash also. + if (chars_left < 2) { + j--; // stay where we are. + continue; + } else { + next_break = j + chars_left - 2; + chars_added = next_break - j + 1; + if (next_break >= input.length()) + next_break = input.length() - 1; + else if (next_break < j) + next_break = j; + add_dash = true; + } + } + } + + astring adding_chunk = input.substring(j, next_break); + // this is what we've decided the next word chunk to be added will be. + // we still haven't completely decided where it goes. + + if (break_line) { + col = min_column; + if (add_dash || keep_on_line) { + // include the previous stuff on the same line. + output += adding_chunk; + if (add_dash) output += "-"; + j = next_break; + continue; // done with this case. + } + + // don't include the previous stuff; make it go to the next line. + accumulated = adding_chunk; + if (punctuate || invisible) { + accumulated += " "; + } else if (end_sentence) { + accumulated += " "; + } + j = next_break; + continue; + } + + // add the line normally since it should fit. + output += adding_chunk; + col += chars_added + punct_adder; // add the characters added. + j = next_break; + just_had_break = false; // reset the state. + + // handle when we processed an invisible or punctuation character. + if (punctuate || invisible) { + output += " "; + } else if (end_sentence) { + output += " "; + } + } + // make sure we handle any leftovers. + if (accumulated.length()) { + output.strip_spaces(astring::FROM_END); + output += parser_bits::platform_eol_to_chars(); + output += indent_add; + output += accumulated; + } + output.strip_spaces(astring::FROM_END); + output += parser_bits::platform_eol_to_chars(); +} + +char string_manipulation::hex_to_char(abyte to_convert) +{ + if (to_convert <= 9) return char('0' + to_convert); + else if ( (to_convert >= 10) && (to_convert <= 15) ) + return char('A' - 10 + to_convert); + else return '?'; +} + +abyte string_manipulation::char_to_hex(char to_convert) +{ + if ( (to_convert >= '0') && (to_convert <= '9') ) + return char(to_convert - '0'); + else if ( (to_convert >= 'a') && (to_convert <= 'f') ) + return char(to_convert - 'a' + 10); + else if ( (to_convert >= 'A') && (to_convert <= 'F') ) + return char(to_convert - 'A' + 10); + else return 0; +} + +byte_array string_manipulation::string_to_hex(const astring &to_convert) +{ + byte_array to_return(0, NIL); + for (int i = 0; i < to_convert.length() / 2; i++) { + int str_index = i * 2; + abyte first_byte = char_to_hex(to_convert.get(str_index)); + abyte second_byte = char_to_hex(to_convert.get(str_index + 1)); + abyte to_stuff = abyte(first_byte * 16 + second_byte); + to_return.concatenate(to_stuff); + } + return to_return; +} + +astring string_manipulation::hex_to_string(const byte_array &to_convert) +{ + astring to_return; + for (int i = 0; i < to_convert.length() * 2; i += 2) { + int str_index = i / 2; + char first_char = hex_to_char(char(to_convert.get(str_index) / 16)); + char second_char = hex_to_char(char(to_convert.get(str_index) % 16)); + to_return += astring(first_char, 1); + to_return += astring(second_char, 1); + } + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/textual/string_manipulation.h b/nucleus/library/textual/string_manipulation.h new file mode 100644 index 00000000..c240f0fb --- /dev/null +++ b/nucleus/library/textual/string_manipulation.h @@ -0,0 +1,88 @@ +#ifndef STRING_MANIPULATION_CLASS +#define STRING_MANIPULATION_CLASS + +/*****************************************************************************\ +* * +* Name : string_manipulation * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +namespace textual { + +//! Provides various functions for massaging strings. + +class string_manipulation +{ +public: + +////////////// + + static basis::astring make_random_name(int min = 1, int max = 64); + //!< creates a random name, where the letters are between 'a' and 'z'. + /*!< the underscore will also be used occasionally. the size is random, + but the minimum size is "min" while the maximum is "max". */ + +////////////// + + static basis::astring long_line(char line_item = '/', int repeat = 76); + //!< produces a long line of "line_item" characters. + /*!< returns a string of text that is somewhat long compared to an 80 column + output window and which consists of a single character repeated. the + character used and the repeat count are both variable. */ + + static basis::astring indentation(int spaces); + //!< Returns a string made of white space that is "spaces" long. + +////////////// + + static void carriage_returns_to_spaces(basis::astring &to_strip); + //!< converts carriage returns in "to_strip" into spaces. + /*!< processes the string "to_strip" by replacing all single carriage + returns with spaces and by turning two or more carriage returns into a + single CR plus spaces. */ + +////////////// + + static void split_lines(const basis::astring &input, basis::astring &output, + int min_column = 0, int max_column = 79); + //!< formats blocks of text for a maximum width. + /*!< processes the "input" text by splitting any lines that are longer + than the "max_column". the "min_column" is used to specify how much + indentation should be included. */ + +////////////// + + // numerical manipulation functions: + + static basis::abyte char_to_hex(char to_convert); + //!< Converts a single character into the corresponding hex nibble. + /*!< If the character is not valid, an arbitrary value is returned. */ + static char hex_to_char(basis::abyte to_convert); + //!< Converts a byte between 0 and 15 into a corresponding hexadecimal character. + + static basis::byte_array string_to_hex(const basis::astring &character_form); + //!< Turns a string form of a set of hex numbers into an array of bytes. + /*!< This functions takes a string in "character_form" and returns an array + of bytes that is half as long and which contains the hexadecimal + interpretation of the string. This is currently geared to even length + strings... */ + static basis::astring hex_to_string(const basis::byte_array &byte_form); + //!< The inverse of string_to_hex prints "byte_form" as text. + /*!< This function takes an array of bytes and converts them into their + equivalent hexadecimal character representation. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/textual/xml_generator.cpp b/nucleus/library/textual/xml_generator.cpp new file mode 100644 index 00000000..ec7645cf --- /dev/null +++ b/nucleus/library/textual/xml_generator.cpp @@ -0,0 +1,261 @@ +/*****************************************************************************\ +* * +* Name : xml_generator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "parser_bits.h" +#include "string_manipulation.h" +#include "xml_generator.h" + +#include +#include +#include + +using namespace basis; +using namespace structures; + +namespace textual { + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s); + +////////////// + +class tag_info +{ +public: + astring _tag_name; + string_table _attribs; + + tag_info() {} + + tag_info(const astring &tag_name, const string_table &attribs) + : _tag_name(tag_name), _attribs(attribs) {} +}; + +////////////// + +class tag_stack : public stack +{ +public: + tag_stack() : stack(0) {} +}; + +////////////// + +xml_generator::xml_generator(int mods) +: _tags(new tag_stack), + _accumulator(new astring), + _human_read(mods & HUMAN_READABLE), + _clean_chars(mods & CLEAN_ILLEGAL_CHARS), + _indentation(2) +{ +} + +xml_generator::~xml_generator() +{ + WHACK(_tags); + WHACK(_accumulator); +} + +const char *xml_generator::outcome_name(const outcome &to_name) +{ + switch (to_name.value()) { + case ERRONEOUS_TAG: return "ERRONEOUS_TAG"; + default: return common::outcome_name(to_name); + } +} + +void xml_generator::set_indentation(int to_indent) +{ + if (to_indent <= 0) to_indent = 1; + _indentation = to_indent; +} + +void xml_generator::reset() +{ + _accumulator->reset(); +// we need a reset on stack. + while (_tags->pop() == common::OKAY) {} +} + +astring xml_generator::generate() +{ + astring to_return; + generate(to_return); + return to_return; +} + +void xml_generator::generate(astring &generated) +{ + close_all_tags(); + generated = ""; // first string is the version. + if (_human_read) generated += parser_bits::platform_eol_to_chars(); + generated += *_accumulator; +} + +outcome xml_generator::open_tag(const astring &tag_name) +{ + string_table junk; + return open_tag(tag_name, junk); +} + +outcome xml_generator::add_header(const astring &tag_name, + const string_table &attributes) +{ + tag_info new_item(tag_name, attributes); + print_open_tag(new_item, HEADER_TAG); + return OKAY; +} + +outcome xml_generator::open_tag(const astring &tag_name, const string_table &attributes) +{ + tag_info new_item(tag_name, attributes); + print_open_tag(new_item); + _tags->push(new_item); + return OKAY; +} + +outcome xml_generator::close_tag(const astring &tag_name) +{ + if (_tags->elements() < 1) return NOT_FOUND; + // check to see that it's the right one to close. + if (_tags->top()._tag_name != tag_name) return ERRONEOUS_TAG; + print_close_tag(tag_name); + _tags->pop(); + return OKAY; +} + +void xml_generator::close_all_tags() +{ + while (_tags->elements()) { + close_tag(_tags->top()._tag_name); + } +} + +outcome xml_generator::add_content(const astring &content) +{ + if (_human_read) { + astring indentata = string_manipulation::indentation(_indentation); + int num_indents = _tags->elements(); + for (int i = 0; i < num_indents; i++) + *_accumulator += indentata; + } + if (_clean_chars) + *_accumulator += clean_reserved(content); + else + *_accumulator += content; + if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); + return OKAY; +} + +void xml_generator::print_open_tag(const tag_info &to_print, int type) +{ + bool is_header = false; + if (type == HEADER_TAG) is_header = true; + + if (_human_read) { +//hmmm: starting to look like a nice macro for this stuff, param is num levs. + astring indentata = string_manipulation::indentation(_indentation); + int num_indents = _tags->elements(); + for (int i = 0; i < num_indents; i++) + *_accumulator += indentata; + } + + if (is_header) + *_accumulator += ""; + else + *_accumulator += ">"; + if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); +} + +void xml_generator::print_close_tag(const astring &tag_name) +{ + if (_human_read) { + astring indentata = string_manipulation::indentation(_indentation); + int num_indents = _tags->elements() - 1; + for (int i = 0; i < num_indents; i++) + *_accumulator += indentata; + } + *_accumulator += ""; + if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars(); +} + +#define PLUGIN_REPLACEMENT(posn, repl_string) { \ + to_modify.zap(posn, posn); \ + to_modify.insert(posn, repl_string); \ + posn += int(strlen(repl_string)) - 1; \ +} + +void xml_generator::clean_reserved_mod(astring &to_modify, + bool replace_spaces) +{ +//could this set live somewhere? + const char *quot = """; + const char *amp = "&"; + const char *lt = "<"; + const char *gt = ">"; + const char *apos = "'"; + const char *space = "_"; + // was going to use %20 but that still won't parse in an attribute name. + + for (int i = 0; i < to_modify.length(); i++) { + switch (to_modify[i]) { + case '"': PLUGIN_REPLACEMENT(i, quot); break; + case '&': PLUGIN_REPLACEMENT(i, amp); break; + case '<': PLUGIN_REPLACEMENT(i, lt); break; + case '>': PLUGIN_REPLACEMENT(i, gt); break; + case '\'': PLUGIN_REPLACEMENT(i, apos); break; + case ' ': if (replace_spaces) PLUGIN_REPLACEMENT(i, space); break; + default: continue; + } + } +} + +astring xml_generator::clean_reserved(const astring &to_modify, + bool replace_spaces) +{ + astring to_return = to_modify; + clean_reserved_mod(to_return, replace_spaces); + return to_return; +} + +} //namespace. + + diff --git a/nucleus/library/textual/xml_generator.h b/nucleus/library/textual/xml_generator.h new file mode 100644 index 00000000..0b3901a6 --- /dev/null +++ b/nucleus/library/textual/xml_generator.h @@ -0,0 +1,129 @@ +#ifndef XML_GENERATOR_CLASS +#define XML_GENERATOR_CLASS + +/*****************************************************************************\ +* * +* Name : xml_generator * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace textual { + +class tag_info; +class tag_stack; + +//! Supports simple XML output with consistency checking. + +class xml_generator +{ +public: + enum behavioral_mods { + HUMAN_READABLE = 0x1, + CLEAN_ILLEGAL_CHARS = 0x2 + }; + + xml_generator(int modifiers = HUMAN_READABLE | CLEAN_ILLEGAL_CHARS); + //!< creates an xml generator with the specified behavior. + + virtual ~xml_generator(); + + DEFINE_CLASS_NAME("xml_generator"); + + //! the possible ways that operations here can complete. + enum outcomes { + OKAY = basis::common::OKAY, + NOT_FOUND = basis::common::NOT_FOUND, + ERRONEOUS_TAG = basis::common::INVALID //temporary until we can shed a compatibility concern. +// DEF INE_OUTCOME(ERRONEOUS_TAG, -75, "The most recently opened tag must be " +// "closed before a new tag can be opened and before any other tag can " +// "be closed"), + }; + + static const char *outcome_name(const basis::outcome &to_name); + //!< reports the string version of "to_name". + + void reset(); //!< throws out all accumulated information. + + basis::astring generate(); + //!< writes the current state into a string and returns it. + /*!< if there was an error during generation, the string will be empty. + note that unclosed tags are not considered an error; they will simply be + closed. note that the accumulated string is not cleared after the + generate() invocation. use reset() to clear out all prior state. */ + + void generate(basis::astring &generated); + //!< synonym method, writes the current state into "generated". + + basis::outcome add_header(const basis::astring &tag_name, const structures::string_table &attributes); + //!< adds an xml style header with the "tag_name" and "attributes". + /*!< headers can be located anywhere in the file. */ + + basis::outcome open_tag(const basis::astring &tag_name, const structures::string_table &attributes); + //!< adds a tag with "tag_name" and the "attributes", if any. + /*!< this adds an item into the output string in the form: @code + @endcode + it is required that you close the tag later on, after the tag's contents + have been added. */ + + basis::outcome open_tag(const basis::astring &tag_name); + //!< adds a tag with "tag_name" without any attributes. + + basis::outcome close_tag(const basis::astring &tag_name); + //!< closes a previously added "tag_name". + /*!< this will generate xml code like so: @code + @endcode + note that it is an error to try to close any tag but the most recently + opened one. */ + + void close_all_tags(); + //!< a wide-bore method that closes all outstanding tags. + + basis::outcome add_content(const basis::astring &content); + //!< stores content into the currently opened tag. + /*!< it is an error to add content when no tag is open. */ + + void set_indentation(int to_indent); + //!< sets the number of spaces to indent for the human readable form. + + static basis::astring clean_reserved(const basis::astring &to_modify, + bool replace_spaces = false); + //!< returns a cleaned version of "to_modify" to make it XML appropriate. + /*!< if "replace_spaces" is true, then any spaces will be turned into + their html code equivalent; this helps in attribute names. */ + + static void clean_reserved_mod(basis::astring &to_modify, + bool replace_spaces = false); + //!< ensures that "to_modify" contains only characters valid for XML. + /*!< this is only different from the other clean method because this + one modifies the string in place. */ + +private: + tag_stack *_tags; //!< the already opened tags. + basis::astring *_accumulator; //!< stores our output. + bool _human_read; //!< true if the output should be human readable. + bool _clean_chars; //!< true if strings should be validated and repaired. + int _indentation; //!< number of spaces per level of xml. + + enum open_types { NORMAL_TAG, HEADER_TAG }; + void print_open_tag(const tag_info &to_print, int type = NORMAL_TAG); + //!< opens the tag for to_print by showing the tag name and attributes. + void print_close_tag(const basis::astring &tag_name); + //!< closes the tag for "tag_name" in the output string. +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/textual/xml_parser.cpp b/nucleus/library/textual/xml_parser.cpp new file mode 100644 index 00000000..f20806db --- /dev/null +++ b/nucleus/library/textual/xml_parser.cpp @@ -0,0 +1,127 @@ +/*****************************************************************************\ +* * +* Name : xml_parser * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "xml_parser.h" + +#include +#include + +using namespace basis; +using namespace structures; + +namespace textual { + +xml_parser::xml_parser(const astring &to_parse) +{ + if (!to_parse) {} +} + +xml_parser::~xml_parser() +{ +} + +const char *xml_parser::outcome_name(const outcome &to_name) +{ + return common::outcome_name(to_name); +} + +void xml_parser::reset(const astring &to_parse) +{ + if (!to_parse) {} +} + +outcome xml_parser::header_callback(astring &header_name, + string_table &attributes) +{ + if (!header_name || !attributes.symbols()) {} + return common::OKAY; +} + + +outcome xml_parser::tag_open_callback(astring &tag_name, + string_table &attributes) +{ + if (!tag_name || !attributes.symbols()) {} + return OKAY; +} + +outcome xml_parser::tag_close_callback(astring &tag_name) +{ + if (!tag_name) {} + return OKAY; +} + +outcome xml_parser::content_callback(astring &content) +{ + if (!content) {} + return OKAY; +} + +outcome xml_parser::parse() +{ + +//phases: we are initially always seeking a bracket bounded tag of some sort. + +// the first few constructs must be headers, especially the xml header. + +// is it true that headers are the only valid thing to see before real tags +// start, or can there be raw content embedded in there? +// yes, it seems to be true in mozilla. you can't have bare content in +// between the headers and the real tags. + +// actually, if we allow the file to not start with the xml header and +// version, then that's a bug. + +// need function to accumulate the tag based on structure. do headers +// have to have a ? as part of the inner and outer structure? + +// test against mozilla to ensure we are seeing the same things; get +// together some tasty sample files. + +// count lines and chars so we can report where it tanked. + +// back to phases, (not a precise grammar below) +// white_space ::= [ ' ' '\n' '\r' '\t' ] * +// ws ::= white_space +// text_phrase ::= not_white_space_nor_reserved not_reserved* +// name ::= text_phrase +// value ::= not_reserved * +// lt_char ::= '<' +// quote ::= '"' + +// xml_file ::= header+ ws tagged_unit+ ws +// header ::= '<' '?' name ws attrib_list ws '?' '>' ws +// tagged_unit ::= open_tag content* close_tag ws +// content ::= [ tagged_unit | text_phrase ] + ws +// open_tag ::= '<' name ws attrib_list ws '>' ws +// attrib_list ::= ( attribute ws ) * ws +// attribute ::= name ws '=' ws quoted_string ws +// quoted_string ::= '"' not_lt_char_nor_quote '"' ws +// close_tag :: '<' '/' name ws '>' ws + +//write a recursive descent parser on this grammar and spit out the +// productions as callbacks, at least for the major ones already listed. + +return common::NOT_IMPLEMENTED; +} + +/* callbacks to invoke. +outcome header_callback(astring &header_name, string_table &attributes) +outcome tag_open_callback(astring &tag_name, string_table &attributes) +outcome tag_close_callback(astring &tag_name) +outcome content_callback(astring &content) +*/ + +} //namespace. + diff --git a/nucleus/library/textual/xml_parser.h b/nucleus/library/textual/xml_parser.h new file mode 100644 index 00000000..7b2011d8 --- /dev/null +++ b/nucleus/library/textual/xml_parser.h @@ -0,0 +1,82 @@ +#ifndef XML_PARSER_CLASS +#define XML_PARSER_CLASS + +/*****************************************************************************\ +* * +* Name : xml_parser * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include + +// forward. +#include +#include + +namespace textual { + +//! Parses XML input and invokes a callback for the different syntactic pieces. + +// hmmm, could this be the first class ever named this? perhaps it should be +// in a textual namespace. -->after current sprint. + +class xml_parser +{ +public: + xml_parser(const basis::astring &to_parse); + virtual ~xml_parser(); + + DEFINE_CLASS_NAME("xml_parser"); + + //! the possible ways that operations here can complete. + enum outcomes { + OKAY = basis::common::OKAY +//uhhh... + }; + + static const char *outcome_name(const basis::outcome &to_name); + //!< reports the string version of "to_name". + + void reset(const basis::astring &to_parse); + //!< throws out any accumulated information and uses "to_parse" instead. + + basis::outcome parse(); + //!< starts the parsing process on the current string. + /*!< this will cause callbacks to be invoked for each of the xml syntactic + elements. */ + + virtual basis::outcome header_callback(basis::astring &header_name, + structures::string_table &attributes); + //!< invoked when a well-formed xml header is seen in the input stream. + /*!< the following applies to all of the callbacks: the derived method must + return an outcome, which will be used by the parser. if the outcome is + OKAY, then parsing will continue. any other outcome will cause parsing + to stop and will become the return value of the parse() method. */ + + virtual basis::outcome tag_open_callback(basis::astring &tag_name, + structures::string_table &attributes); + //!< an xml tag has been opened in the input stream. + + virtual basis::outcome tag_close_callback(basis::astring &tag_name); + //!< an xml tag was closed in the input stream. + + virtual basis::outcome content_callback(basis::astring &content); + //!< invoked when plain text content is found inside an opened tag. + +private: + basis::astring *_xml_stream; // the stringful of xml information. + +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/timely/earth_time.cpp b/nucleus/library/timely/earth_time.cpp new file mode 100644 index 00000000..ed7c4583 --- /dev/null +++ b/nucleus/library/timely/earth_time.cpp @@ -0,0 +1,406 @@ +/*****************************************************************************\ +* * +* Name : earth_time * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1999-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "earth_time.h" + +#include +#include + +#include +#if defined(__WIN32__) || defined(__UNIX__) + #include +#endif + +using namespace basis; +using namespace structures; + +namespace timely { + +const int days_in_month[12] + = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +const int leap_days_in_month[12] + = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +const int julian_days_in_month[12] + = { 31, 29, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 }; +//hmmm: is this right? + +const int julian_leap_days_in_month[12] + = { 31, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30 }; + +////////////// + +void clock_time::pack(byte_array &packed_form) const +{ + attach(packed_form, hour); + attach(packed_form, minute); + attach(packed_form, second); + attach(packed_form, millisecond); + attach(packed_form, microsecond); +} + +bool clock_time::unpack(byte_array &packed_form) +{ + if (!detach(packed_form, hour)) return false; + if (!detach(packed_form, minute)) return false; + if (!detach(packed_form, second)) return false; + if (!detach(packed_form, millisecond)) return false; + if (!detach(packed_form, microsecond)) return false; + return true; +} + +#define EASY_LT(x, y) \ + if (x < y) return true; \ + if (x > y) return false + +bool clock_time::operator < (const clock_time &to_compare) const +{ + EASY_LT(hour, to_compare.hour); + EASY_LT(minute, to_compare.minute); + EASY_LT(second, to_compare.second); + EASY_LT(millisecond, to_compare.millisecond); + EASY_LT(microsecond, to_compare.microsecond); + return false; +} + +bool clock_time::operator == (const clock_time &to_compare) const +{ + return (hour == to_compare.hour) + && (minute == to_compare.minute) + && (second == to_compare.second) + && (millisecond == to_compare.millisecond) + && (microsecond == to_compare.microsecond); +} + +astring clock_time::text_form(int how) const +{ + astring to_return; + text_form(to_return, how); + return to_return; +} + +void clock_time::text_form(astring &to_return, int how) const +{ + if (!how) return; // enforce use of the default. + if (how & MILITARY) + to_return += a_sprintf("%02d:%02d", hour, minute); + else { + int uhr = hour; + if (uhr > 12) uhr -= 12; + to_return += a_sprintf("%2d:%02d", uhr, minute); + } + if ( (how & SECONDS) || (how & MILLISECONDS) ) + to_return += a_sprintf(":%02d", second); + if (how & MILLISECONDS) + to_return += a_sprintf(":%03d", millisecond); + if (how & MERIDIAN) { + if (hour >= 12) to_return += "PM"; + else to_return += "AM"; + } +} + +// makes sure that "val" is not larger than "max". if it is, then max is +// used as a divisor and stored in "rolls". +#define limit_value(val, max) \ + if (val < 0) { \ + rolls = val / max; \ + rolls--; /* subtract an extra one since we definitely roll before -max */ \ + val += max * -rolls; \ + } else if (val >= max) { \ + rolls = val / max; \ + val -= max * rolls; \ + } else { rolls = 0; } + +int clock_time::normalize(clock_time &to_fix) +{ + int rolls = 0; // rollover counter. + limit_value(to_fix.microsecond, 1000); + to_fix.millisecond += rolls; + limit_value(to_fix.millisecond, 1000); + to_fix.second += rolls; + limit_value(to_fix.second, 60); + to_fix.minute += rolls; + limit_value(to_fix.minute, 60); + to_fix.hour += rolls; + limit_value(to_fix.hour, 24); + return rolls; +} + +////////////// + +void day_in_year::pack(byte_array &packed_form) const +{ + attach(packed_form, day_of_year); + attach(packed_form, abyte(day_of_week)); + attach(packed_form, abyte(month)); + attach(packed_form, day_in_month); + attach(packed_form, abyte(1)); + // still packing dst chunk; must for backward compatibility. +} + +bool day_in_year::unpack(byte_array &packed_form) +{ + if (!detach(packed_form, day_of_year)) return false; + abyte temp; + if (!detach(packed_form, temp)) return false; + day_of_week = days(temp); + if (!detach(packed_form, temp)) return false; + month = months(temp); + if (!detach(packed_form, day_in_month)) return false; + if (!detach(packed_form, temp)) return false; // dst chunk--backward compat. + return true; +} + +bool day_in_year::operator < (const day_in_year &to_compare) const +{ + EASY_LT(month, to_compare.month); + EASY_LT(day_in_month, to_compare.day_in_month); + return false; +} + +bool day_in_year::operator == (const day_in_year &to_compare) const +{ + return (month == to_compare.month) + && (day_in_month == to_compare.day_in_month); +} + +astring day_in_year::text_form(int how) const +{ + astring to_return; + text_form(to_return, how); + return to_return; +} + +void day_in_year::text_form(astring &to_stuff, int how) const +{ + if (!how) return; // enforce use of the default. + if (how & INCLUDE_DAY) to_stuff += astring(day_name(day_of_week)) + " "; + const char *monat = short_month_name(month); + if (how & LONG_MONTH) + monat = month_name(month); +//hmmm: more formatting, like euro? + to_stuff += monat; + to_stuff += a_sprintf(" %02d", day_in_month); +} + +// note: this only works when adjusting across one month, not multiples. +int limit_day_of_month(int &day, int days_in_month, int days_in_prev_month) +{ + if (day > days_in_month) { + day -= days_in_month; + return 1; // forward rollover. + } else if (day < 1) { + day += days_in_prev_month; + return -1; + } + return 0; // no rolling. +} + +int day_in_year::normalize(day_in_year &to_fix, bool leap_year) +{ + int rolls = 0; // rollover counter. + int daysinm = leap_year? + leap_days_in_month[to_fix.month] : days_in_month[to_fix.month]; + int prev_month = to_fix.month - 1; + if (prev_month < 0) prev_month = 11; + int daysinpm = leap_year? + leap_days_in_month[prev_month] : days_in_month[prev_month]; + rolls = limit_day_of_month(to_fix.day_in_month, daysinm, daysinpm); + int monat = to_fix.month + rolls; + limit_value(monat, 12); // months are zero based. + to_fix.month = months(monat); + return rolls; +} + +////////////// + +void time_locus::pack(byte_array &packed_form) const +{ + attach(packed_form, year); + clock_time::pack(packed_form); + day_in_year::pack(packed_form); +} + +bool time_locus::unpack(byte_array &packed_form) +{ + if (!detach(packed_form, year)) return false; + if (!clock_time::unpack(packed_form)) return false; + if (!day_in_year::unpack(packed_form)) return false; + return true; +} + +astring time_locus::text_form_long(int t, int d, int y) const +{ + astring to_return; + text_form_long(to_return, t, d, y); + return to_return; +} + +bool time_locus::equal_to(const equalizable &s2) const { + const time_locus *s2_cast = dynamic_cast(&s2); + if (!s2_cast) throw "error: time_locus::==: unknown type"; + return (year == s2_cast->year) + && ( (const day_in_year &) *this == *s2_cast) + && ( (const clock_time &) *this == *s2_cast); +} + +bool time_locus::less_than(const orderable &s2) const { + const time_locus *s2_cast = dynamic_cast(&s2); + if (!s2_cast) throw "error: time_locus::<: unknown type"; + EASY_LT(year, s2_cast->year); + if (day_in_year::operator < (*s2_cast)) return true; + if (!(day_in_year::operator == (*s2_cast))) return false; + if (clock_time::operator < (*s2_cast)) return true; + return false; +} + +void time_locus::text_form_long(astring &to_stuff, int t, int d, int y) const +{ +//hmmm: more formatting desired, like european. + if (!y) { + text_form_long(to_stuff, t, d); // enforce use of the default. + return; + } + // add the day. + day_in_year::text_form(to_stuff, d); + to_stuff += " "; + // add the year. + if (y & SHORT_YEAR) + to_stuff += a_sprintf("%2d", year % 100); + else + to_stuff += a_sprintf("%4d", year); + // add the time. + to_stuff += " "; + clock_time::text_form(to_stuff, t); +} + +int time_locus::normalize(time_locus &to_fix) +{ + int rolls = clock_time::normalize(to_fix); + to_fix.day_in_month += rolls; + +//hmmm: this little gem should be abstracted to a method. + bool leaping = !(to_fix.year % 4); + if (!(to_fix.year % 100)) leaping = false; + if (!(to_fix.year % 400)) leaping = true; + + rolls = day_in_year::normalize(to_fix, leaping); + to_fix.year += rolls; + return 0; + // is that always right? not for underflow. +//hmmm: resolve the issue of rollovers here. +} + +////////////// + +time_locus convert(const tm &to_convert, int ms) +{ + time_locus r; + + // we lack the resolution for this, currently. + r.microsecond = 0; + + r.second = to_convert.tm_sec; + r.minute = to_convert.tm_min; + r.hour = to_convert.tm_hour; + r.day_in_month = to_convert.tm_mday; + r.month = months(to_convert.tm_mon); + r.year = to_convert.tm_year + 1900; + r.day_of_week = days(to_convert.tm_wday); + r.day_of_year = to_convert.tm_yday; + r.millisecond = ms; + return r; +} + +time_locus now() +{ + timeb current; + ftime(¤t); + tm split_time(*localtime(¤t.time)); + return convert(split_time, current.millitm); +} + +time_locus greenwich_now() +{ + timeb current; + ftime(¤t); + tm split_time(*gmtime(¤t.time)); + return convert(split_time, current.millitm); +} + +clock_time time_now() { return now(); } + +days day_now() { return now().day_of_week; } + +months month_now() { return now().month; } + +int year_now() { return now().year; } + +day_in_year date_now() { return now(); } + +const char *day_name(days to_name) +{ + switch (to_name) { + case SUNDAY: return "Sunday"; + case MONDAY: return "Monday"; + case TUESDAY: return "Tuesday"; + case WEDNESDAY: return "Wednesday"; + case THURSDAY: return "Thursday"; + case FRIDAY: return "Friday"; + case SATURDAY: return "Saturday"; + default: return "Not_a_day"; + } +} + +const char *month_name(months to_name) +{ + switch (to_name) { + case JANUARY: return "January"; + case FEBRUARY: return "February"; + case MARCH: return "March"; + case APRIL: return "April"; + case MAY: return "May"; + case JUNE: return "June"; + case JULY: return "July"; + case AUGUST: return "August"; + case SEPTEMBER: return "September"; + case OCTOBER: return "October"; + case NOVEMBER: return "November"; + case DECEMBER: return "December"; + default: return "Not_a_month"; + } +} + +const char *short_month_name(months to_name) +{ + switch (to_name) { + case JANUARY: return "Jan"; + case FEBRUARY: return "Feb"; + case MARCH: return "Mar"; + case APRIL: return "Apr"; + case MAY: return "May"; + case JUNE: return "Jun"; + case JULY: return "Jul"; + case AUGUST: return "Aug"; + case SEPTEMBER: return "Sep"; + case OCTOBER: return "Oct"; + case NOVEMBER: return "Nov"; + case DECEMBER: return "Dec"; + default: return "Not"; + } +} + +} // namespace. + diff --git a/nucleus/library/timely/earth_time.h b/nucleus/library/timely/earth_time.h new file mode 100644 index 00000000..3b249413 --- /dev/null +++ b/nucleus/library/timely/earth_time.h @@ -0,0 +1,241 @@ +#ifndef EARTH_TIME_GROUP +#define EARTH_TIME_GROUP + +// Name : earth_time +// Author : Chris Koeritz +/****************************************************************************** +* Copyright (c) 1999-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +//! A set of methods for rendering calendrical and clock times. +/*! + It is based on the Gregorian calendar currently in use by the USA and other + countries. +*/ + +namespace timely { + + enum days { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }; + //!< The names of the days of the week. + + days day_now(); //!< Returns the current local day. + + const char *day_name(days to_name); + //!< Returns the name of the day "to_name". + + enum months { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, + SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER }; + //!< The names of the months in our calendar. + + months month_now(); //!< returns the local month. + + const char *month_name(months to_name); + //!< Returns the name of the month "to_name". + const char *short_month_name(months to_name); + //!< Returns a shorter, constant-length (3 characters) month name. + + extern const int days_in_month[12]; + //!< The number of days in each month in the standard year. + extern const int leap_days_in_month[12]; + //!< The number of days in each month in a leap year. + + extern const int julian_days_in_month[12]; + //!< Number of days in each month based on the julian calendar. + extern const int julian_leap_days_in_month[12]; + //!< Number of days in each month of a leap year in the julian calendar. + + const int SECONDS_IN_MINUTE = 60; //!< Number of seconds in one minute. + const int MINUTES_IN_HOUR = 60; //!< Number of minutes in an hour. + const int HOURS_IN_DAY = 24; //!< Number of hours in a day. + const int DAYS_IN_YEAR = 365; //!< Number of days in a standard year. + const int LEAP_DAYS_IN_YEAR = 366; //!< Number of days in a leap year. + const double APPROX_DAYS_IN_YEAR = 365.2424; + //!< A more accurate measure of the number of days in a year. + /*!< This is the more accurate mean length of time in 24 hour days between + vernal equinoxes. it's about 11 minutes shy of 365.25 days. */ + + //! An enumeration of time zones, both relative and absolute. + enum time_zones { + LOCAL_ZONE, //!< The time zone this computer is configured to report. + GREENWICH_ZONE //!< The time zone of Greenwich Mean Time. + }; + + // now some structures for representing time... + + //! A specific point in time as represented by a 24 hour clock. + class clock_time : public virtual basis::packable + { + public: + int hour; //!< The hour represented in military time: 0 through 23. + int minute; //!< The number of minutes after the hour. + int second; //!< The number of seconds after the current minute. + int millisecond; //!< The number of milliseconds elapsed in this second. + int microsecond; //!< Number of microseconds elapsed in this millisecond. + + //! Constructs a clock_time object given all the parts. + clock_time(int h = 0, int m = 0, int s = 0, int ms = 0, int us = 0) + : hour(h), minute(m), second(s), millisecond(ms), + microsecond(us) {} + ~clock_time() {} + + int packed_size() const { return 5 * structures::PACKED_SIZE_INT32; } + + virtual void pack(basis::byte_array &packed_form) const; + //!< Packs a clock time into an array of bytes. + virtual bool unpack(basis::byte_array &packed_form); + //!< Unpacks a clock time from an array of bytes. + + bool operator < (const clock_time &to_compare) const; + //!< Returns true if this clock_time is earlier than "to_compare" + bool operator == (const clock_time &to_compare) const; + //!< Returns true if this clock_time is equal to "to_compare" + + //! An enumeration of time formatting modes used when printing the time. + enum time_formats { + MERIDIAN = 0x1, //!< default: uses 12 hour with AM/PM and no seconds. + MILITARY = 0x2, //!< use military 24 hour time. + NO_AM_PM = 0x4, //!< use 12 hour time but don't include AM/PM. + SECONDS = 0x8, //!< include the number of seconds as a third field. + MILLISECONDS = 0x10 //!< milliseconds are fourth field (after secs). + }; + + basis::astring text_form(int how = MERIDIAN) const; + //!< Prints the clock_time according to "how". + /*!< "how" is a combination of time_formats. */ + void text_form(basis::astring &to_stuff, int how = MERIDIAN) const; + //!< Prints the time into "to_stuff" given "how". + /*!< note that "to_stuff" will be appended to; the existing contents + are retained. */ + + static int normalize(clock_time &to_fix); + // ensures that the units in each field are in the proper range by + // promoting them upwards. if the clock_time goes above the maximum hour, + // then it rolls around. zero is returned for no rollover or a positive + // integer is returned for the number of rollovers that occurred. if + // there are any negative fields, they are rolled backwards. + // the returned rollovers are measured in days. + }; + + //! An object that represents a particular day in a year. + class day_in_year : public virtual basis::packable + { + public: + months month; //!< The current month. + int day_in_month; //!< The day number within the month (starting at one). + days day_of_week; //!< The day of the week. + int day_of_year; //!< Numerical day, where January 1st is equal to zero. + + int packed_size() const { return 4 * structures::PACKED_SIZE_INT32; } + + //! Constructs a representation of the day specified. + day_in_year(months m = JANUARY, int dim = 1, days dow = SUNDAY, + int day_o_year = 1) : month(m), day_in_month(dim), + day_of_week(dow), day_of_year(day_o_year) {} + + virtual void pack(basis::byte_array &packed_form) const; + //!< Packs a day object into an array of bytes. + virtual bool unpack(basis::byte_array &packed_form); + //!< Unpacks a day object from an array of bytes. + + bool operator < (const day_in_year &to_compare) const; + //!< Returns true if this day is earlier than "to_compare" + /*!< Note that this only compares the month and day in the month. */ + bool operator == (const day_in_year &to_compare) const; + //!< Returns true if this day is equal to "to_compare" + /*!< Note that this only compares the month and day in the month. */ + + //! An enumeration of ways to print out the current date. + enum date_formats { + // note: these classes may need to be revised in the year 9999. + SHORT_MONTH = 0x1, //!< default: three letter month. + LONG_MONTH = 0x2, //!< uses full month name. + INCLUDE_DAY = 0x4 //!< adds the name of the day. + }; + + basis::astring text_form(int how = SHORT_MONTH) const; + //!< Prints the day according to "how". + void text_form(basis::astring &to_stuff, int how = SHORT_MONTH) const; + //!< Prints the day according to "how" and stores it in "to_stuff". + + static int normalize(day_in_year &to_fix, bool leap_year = false); + //!< normalizes the day as needed and returns the adjustment in years. + /*!< note that this only adjusts the day_in_month and month members + currently. the other counters are not changed. */ + }; + + //! An object that represents a particular point in time. + /*! It contains both a time of day and the day in the year. */ + class time_locus : public clock_time, public day_in_year, + public virtual basis::hoople_standard + { + public: + int year; //!< The year, using the gregorian calendar. + + time_locus() : clock_time(), day_in_year(), year() {} + + DEFINE_CLASS_NAME("time_locus"); + + //! Constructs a location in time given its components. + time_locus(const clock_time &ct, const day_in_year &ytd, int year_in) + : clock_time(ct), day_in_year(ytd), year(year_in) {} + + int packed_size() const { return clock_time::packed_size() + + day_in_year::packed_size() + structures::PACKED_SIZE_INT32; } + + virtual void pack(basis::byte_array &packed_form) const; + //!< Packs a time_locus object into an array of bytes. + virtual bool unpack(basis::byte_array &packed_form); + //!< Unpacks a time_locus object from an array of bytes. + + // these implement the orderable and equalizable interfaces. + virtual bool equal_to(const basis::equalizable &s2) const; + virtual bool less_than(const basis::orderable &s2) const; +//old bool operator < (const time_locus &to_compare) const; + //!< Returns true if this time_locus is earlier than "to_compare" +//old bool operator == (const time_locus &to_compare) const; + //!< Returns true if this time_locus is equal to "to_compare" + + //! Enumerates the ways to show the year. + enum locus_formats { + LONG_YEAR = 0x1, //!< default: full four digit year (problems in 9999). + SHORT_YEAR = 0x2 //!< use only last two digits of year. ugh--Y2K danger. + }; + + // fulfills obligation for text_formable. + virtual void text_form(basis::base_string &state_fill) const { + state_fill.assign(text_form_long(clock_time::MERIDIAN, day_in_year::SHORT_MONTH, LONG_YEAR)); + } + + basis::astring text_form_long(int t = clock_time::MERIDIAN, + int d = day_in_year::SHORT_MONTH, int y = LONG_YEAR) const; + //! Prints out the time_locus given the way to print each component. + /*< "t" is a combination of time_formats, "d" is a combination of + date_formats and "y" is a combination of locus_formats. */ + void text_form_long(basis::astring &to_stuff, int t = clock_time::MERIDIAN, + int d = day_in_year::SHORT_MONTH, int y = LONG_YEAR) const; + //! Same as text_form() above, but stores into "to_stuff". + + static int normalize(time_locus &to_fix); + //!< normalizes the time_locus for its clock time and date. +//hmmm: what are rollovers measured in? + }; + + int year_now(); //!< what year is it? + clock_time time_now(); //!< what time is it? + day_in_year date_now(); //!< what day on the calendar is it? + time_locus now(); //!< returns our current locus in the time continuum. + time_locus greenwich_now(); //!< returns Greenwich Mean Time (their now). +} // namespace. + +#endif + diff --git a/nucleus/library/timely/makefile b/nucleus/library/timely/makefile new file mode 100644 index 00000000..c297d0f5 --- /dev/null +++ b/nucleus/library/timely/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = timely +TYPE = library +SOURCE = earth_time.cpp stopwatch.cpp time_control.cpp time_stamp.cpp timer_driver.cpp +TARGETS = timely.lib + +include cpp/rules.def + diff --git a/nucleus/library/timely/stopwatch.cpp b/nucleus/library/timely/stopwatch.cpp new file mode 100644 index 00000000..d5661cbc --- /dev/null +++ b/nucleus/library/timely/stopwatch.cpp @@ -0,0 +1,103 @@ +/*********************** +* * +* Name : stopwatch +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "time_stamp.h" +#include "stopwatch.h" + +#include +#include +#include + +using namespace basis; + +namespace timely { + +stopwatch::stopwatch() +: _status(UNSTARTED), + _start_time(new time_stamp()), + _stop_time(new time_stamp()), + _total_so_far(0) +{} + +stopwatch::stopwatch(const stopwatch &to_copy) +: _status(UNSTARTED), + _start_time(new time_stamp()), + _stop_time(new time_stamp()), + _total_so_far(0) +{ *this = to_copy; } + +stopwatch::~stopwatch() +{ + _status = UNSTARTED; + WHACK(_start_time); + WHACK(_stop_time); +} + +stopwatch &stopwatch::operator =(const stopwatch &to_copy) +{ + if (this == &to_copy) return *this; + *_start_time = *to_copy._start_time; + *_stop_time = *to_copy._stop_time; + _status = to_copy._status; + _total_so_far = to_copy._total_so_far; + return *this; +} + +void stopwatch::reset() { _status = UNSTARTED; _total_so_far = 0; } + +int stopwatch::milliseconds() { return common_measure(); } + +void stopwatch::start() +{ + if (_status == RUNNING) return; + *_start_time = time_stamp(); + _status = RUNNING; +} + +int stopwatch::compute_diff(const time_stamp &t1, const time_stamp &t2) +{ return int(t2.value() - t1.value()); } + +void stopwatch::halt() +{ + if (_status == STOPPED) return; + else if (_status == UNSTARTED) return; + + *_stop_time = time_stamp(); + _total_so_far += compute_diff(*_start_time, *_stop_time); + + _status = STOPPED; +} + +int stopwatch::common_measure() +{ + bool restart = false; + int to_return = 0; + switch (_status) { + case UNSTARTED: break; + case RUNNING: + // stop stopwatch, restart afterwards. + halt(); + restart = true; + // intentional fall through to default. + default: + // set the return value to the accumulated time. + to_return = _total_so_far; + break; + } + if (restart) start(); // crank the stopwatch back up if we were supposed to. + return to_return; +} + +} //namespace. + diff --git a/nucleus/library/timely/stopwatch.h b/nucleus/library/timely/stopwatch.h new file mode 100644 index 00000000..964debf7 --- /dev/null +++ b/nucleus/library/timely/stopwatch.h @@ -0,0 +1,94 @@ +#ifndef STOPWATCH_CLASS +#define STOPWATCH_CLASS + +/*** +* +* Name : stopwatch +* Author : Chris Koeritz +******************************************************************************* +* Copyright (c) 1991-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "time_stamp.h" + +namespace timely { + +//! A class for measuring event durations in real time. +/*! + Once the stopwatch is constructed, it can then be repeatedly started and + halted, and then started again. The number of milliseconds or + microseconds elapsed can be requested while the stopwatch is running, but + that can disrupt fine-grained measurements. +*/ + +class stopwatch : public virtual basis::root_object +{ +public: + stopwatch(); + stopwatch(const stopwatch &to_copy); + + virtual ~stopwatch(); + + stopwatch &operator =(const stopwatch &to_copy); + + void start(); + //!< Begins the timing. + /*!< If the stopwatch is already timing, then "start" does nothing. */ + + void halt(); + //!< Stops the timing. + /*!< start() may be called again to resume timing after the halt. If the + stopwatch is already stopped, or never was started, then halt does nothing. */ + void stop() { halt(); } + //!< a synonym for halt(). + + void reset(); + //!< Stops the stopwatch and clears it to zero time elapsed. + + int milliseconds(); + //!< Returns the elapsed number of milliseconds on the stopwatch, overall. + int elapsed() { return milliseconds(); } + //!< a synonym for milliseconds(). + +private: + enum stopwatch_kinds { UNSTARTED, RUNNING, STOPPED }; //!< states for the stopwatch. + stopwatch_kinds _status; //!< our current state. + time_stamp *_start_time; //!< last time we got started. + time_stamp *_stop_time; //!< last time we got stopped. + int _total_so_far; //!< total amount of time run for so far. + + int common_measure(); + //!< returns the current time used to this point, in milliseconds. + + int compute_diff(const time_stamp &t1, const time_stamp &t2); + //!< the difference in milliseconds between the times "t1" and "t2". +}; + +////////////// + +//! Logs a warning when an operation takes longer than expected. +/*! + Place TIME_CHECK_BEGIN before the code that you want to check, then place + TIME_CHECK_END afterwards. The two calls must be in the same scope. + "logger" should be a reference to a log_base object. [ by Brit Minor ] +*/ +#define TIME_CHECK_BEGIN \ + stopwatch t; \ + t.start(); +#define TIME_CHECK_END(logger, who, msec_limit, what, filter) { \ + t.halt(); \ + if (t.milliseconds() > msec_limit) { \ + (logger).log( a_sprintf("TIME_CHECK: %s: %d ms wait for %s.", \ + (who), t.milliseconds(), (what)), filter); \ + } \ +} + +} //namespace. + +#endif + diff --git a/nucleus/library/timely/time_control.cpp b/nucleus/library/timely/time_control.cpp new file mode 100644 index 00000000..17e71ddb --- /dev/null +++ b/nucleus/library/timely/time_control.cpp @@ -0,0 +1,98 @@ +// Name : time_control +// Author : Chris Koeritz +/****************************************************************************** +* Copyright (c) 1994-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "time_control.h" + +#include +#include + +#include +#if defined(__WIN32__) || defined(__UNIX__) + #include +#endif +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace structures; + +namespace timely { + +void time_control::sleep_ms(basis::un_int msec) +{ +#ifdef __UNIX__ + usleep(msec * 1000); +#endif +#ifdef __WIN32__ + Sleep(msec); +#endif +} + +bool time_control::set_time(const time_locus &new_time) +{ +#ifdef __WIN32__ + SYSTEMTIME os_time; + os_time.wYear = WORD(new_time.year); + os_time.wMonth = new_time.month; + os_time.wDayOfWeek = new_time.day_of_week; + os_time.wDay = new_time.day_of_year; + os_time.wHour = new_time.hour; + os_time.wMinute = new_time.minute; + os_time.wSecond = new_time.second; + os_time.wMilliseconds = 0; // currently unused. + + // get our process token for manipulation. + HANDLE petoken; + OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES + | TOKEN_QUERY, &petoken); + // get our +//something or other +// identifier so we can adjust our privileges. + LUID our_id; + LookupPrivilegeValue(NULL, to_unicode_temp("SeSystemTimePrivilege"), &our_id); + // make up a privilege structure for the adjustment. + TOKEN_PRIVILEGES privs; + privs.PrivilegeCount = 1; + privs.Privileges[0].Luid = our_id; + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + // enable system-time privileges. + AdjustTokenPrivileges(petoken, false, &privs, sizeof(TOKEN_PRIVILEGES), + NULL, NULL); + + SetLocalTime(&os_time); // actually set the time. + + // disable the time adjustment privileges again. + AdjustTokenPrivileges(petoken, true, &privs, sizeof(TOKEN_PRIVILEGES), + NULL, NULL); + + // let all the main windows know that the time got adjusted. +//do we need to do this ourselves? + ::PostMessage(HWND_BROADCAST, WM_TIMECHANGE, 0, 0); + +//hmmm: make sure this seems right. + CloseHandle(petoken); + + return true; +#elif defined(__UNIX__) +//no implem yet. + +//temp to shut up warnings +time_locus ted = new_time; +return ted.year ? 0:1; + +#else + return false; +#endif +} + +} // namespace. + diff --git a/nucleus/library/timely/time_control.h b/nucleus/library/timely/time_control.h new file mode 100644 index 00000000..d2391b41 --- /dev/null +++ b/nucleus/library/timely/time_control.h @@ -0,0 +1,48 @@ +#ifndef TIME_CONTROL_CLASS +#define TIME_CONTROL_CLASS + +/*****************************************************************************\ +* * +* Name : time_control +* Author : Chris Koeritz +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "earth_time.h" + +///#include +///#include +///#include + +namespace timely { + +//! Provides some functions that affect time, or ones perception of time. + +class time_control : public virtual basis::nameable +{ +public: + ~time_control() {} + + static void sleep_ms(basis::un_int msec); + //!< a system independent name for a forced snooze measured in milliseconds. + /*!< the application will do nothing on the current thread for the amount of + time specified. on some systems, if "msec" is zero, then the sleep will + yield the current thread's remaining timeslice back to other threads. */ + + bool set_time(const time_locus &new_time); + //!< makes the current time equal to "new_time". + /*!< This will only work if the operation is supported on this OS and if + the current user has the proper privileges. */ +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/timely/time_stamp.cpp b/nucleus/library/timely/time_stamp.cpp new file mode 100644 index 00000000..d015a342 --- /dev/null +++ b/nucleus/library/timely/time_stamp.cpp @@ -0,0 +1,160 @@ +/*****************************************************************************\ +* * +* Name : time_stamp * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "earth_time.h" +#include "time_stamp.h" + +#include +#include + +#include +#ifdef __WIN32__ + #define _WINSOCKAPI_ // make windows.h happy about winsock. +// #include + #include // timeval. +#endif + +using namespace basis; + +namespace timely { + +static mutex &__uptime_synchronizer() { + static mutex uptiming_syncher; + return uptiming_syncher; +} + +basis::astring time_stamp::notarize(bool add_space) +{ + const time_locus the_time = now(); + astring to_return; + the_time.text_form_long(to_return, clock_time::MILITARY | clock_time::MILLISECONDS); + if (add_space) to_return += " "; + return to_return; +} + +time_stamp::time_stamp() : c_stamp(0) { fill_in_time(); } + +time_stamp::time_stamp(time_representation offset) +: c_stamp(0) { reset(offset); } + +void time_stamp::reset() { fill_in_time(); } + +astring time_stamp::text_form(stamp_display_style style) const +{ + time_representation stump = c_stamp; + bool past = false; + if (style == STAMP_RELATIVE) { + // adjust the returned time by subtracting the current time. + stump -= get_time_now(); + if (negative(stump)) { + // if we're negative, just note that the stamp is in the past. + past = true; + stump = absolute_value(stump); + } + } + time_representation divisor = 3600 * SECOND_ms; + basis::un_int hours = basis::un_int(stump / divisor); + stump -= divisor * time_representation(hours); + divisor /= 60; + basis::un_int minutes = basis::un_int(stump / divisor); + stump -= divisor * time_representation(minutes); + divisor /= 60; + basis::un_int seconds = basis::un_int(stump / divisor); + stump -= divisor * time_representation(seconds); + basis::un_int milliseconds = basis::un_int(stump); + // make absolutely sure we are between 0 and 999. + milliseconds %= 1000; + + astring to_return; + bool did_hours = false; + if (hours) { + to_return += astring(astring::SPRINTF, "%uh:", hours); + did_hours = true; + } + if (minutes || did_hours) + to_return += astring(astring::SPRINTF, "%02um:", minutes); + to_return += astring(astring::SPRINTF, "%02us.%03u", seconds, milliseconds); + if (style == STAMP_RELATIVE) { + if (past) to_return += " ago"; + else to_return += " from now"; + } + return to_return; +} + +void time_stamp::fill_in_time() +{ + time_representation current = get_time_now(); + c_stamp = current; // reset our own time now. +} + +void time_stamp::reset(time_representation offset) +{ + fill_in_time(); + c_stamp += offset; +} + +time_stamp::time_representation time_stamp::get_time_now() +{ return rolling_uptime(); } + +const double __rollover_point = 2.0 * MAXINT32; + // this number is our rollover point for 32 bit integers. + +double time_stamp::rolling_uptime() +{ + auto_synchronizer l(__uptime_synchronizer()); + // protect our rollover records. + + static basis::un_int __last_ticks = 0; + static int __rollovers = 0; + + basis::un_int ticks_up = environment::system_uptime(); + // acquire the current uptime as a 32 bit unsigned int. + + if (ticks_up < __last_ticks) { + // rollover happened. increment our tracker. + __rollovers++; + } + __last_ticks = ticks_up; + + return double(__rollovers) * __rollover_point + double(ticks_up); +} + +timeval time_stamp::fill_timeval_ms(int duration) +{ + timeval time_out; // timeval has tv_sec=seconds, tv_usec=microseconds. + if (!duration) { + // duration is immediate for the check; just a quick poll. + time_out.tv_sec = 0; + time_out.tv_usec = 0; +#ifdef DEBUG_PORTABLE +// LOG("no duration specified"); +#endif + } else { + // a non-zero duration means we need to compute secs and usecs. + time_out.tv_sec = duration / 1000; + // set the number of seconds from the input in milliseconds. + duration -= time_out.tv_sec * 1000; + // now take out the chunk we've already recorded as seconds. + time_out.tv_usec = duration * 1000; + // set the number of microseconds from the remaining milliseconds. +#ifdef DEBUG_PORTABLE +// LOG(isprintf("duration of %d ms went to %d sec and %d usec.", duration, +// time_out.tv_sec, time_out.tv_usec)); +#endif + } + return time_out; +} + +} //namespace. + diff --git a/nucleus/library/timely/time_stamp.h b/nucleus/library/timely/time_stamp.h new file mode 100644 index 00000000..691660d8 --- /dev/null +++ b/nucleus/library/timely/time_stamp.h @@ -0,0 +1,113 @@ +#ifndef TIME_STAMP_CLASS +#define TIME_STAMP_CLASS + +/*****************************************************************************\ +* * +* Name : time_stamp * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +// forward. +class rollover_record; +struct timeval; + +namespace timely { + +//! Represents a point in time relative to the operating system startup time. +/*! + This duration is measured in milliseconds. This class provides a handy way + of measuring relative durations at the millisecond time scale. + Unfortunately, operating systems that reckon their millisecond uptime in + 32 bit integers will suffer from a rollover problem every 49 days or so, + but this class corrects this issue. +*/ + +class time_stamp : public virtual basis::orderable +{ +public: + typedef double time_representation; + //!< the representation of time for this universe, measured in milliseconds. + + time_stamp(); + //!< creates a time_stamp containing the current time. + + time_stamp(time_representation offset); + //!< creates a stamp after the current time by "offset" milliseconds. + /*!< negative offsets are allowed, in which case the stamp will be + prior to the current time. */ + + static double rolling_uptime(); + //!< give the OS uptime in a more durable form that handles rollovers. + + void reset(); + //!< sets the stamp time back to now. + void reset(time_representation offset); + //!< sets the stamp forward by "offset" similar to the second constructor. + + time_representation value() const { return c_stamp; } + //!< returns the time_stamp in terms of the lower level type. + + enum stamp_display_style { STAMP_RELATIVE, STAMP_ABSOLUTE }; + + basis::astring text_form(stamp_display_style style = STAMP_RELATIVE) const; + //!< returns a simple textual representation of the time_stamp. + /*!< if the "style" is ABSOLUTE, then the stamp is shown in H:M:S.ms + form based on the system uptime. if the "style" is RELATIVE, then the + stamp is shown as an offset from now. */ + + static basis::astring notarize(bool add_space = true); + //!< a useful method for getting a textual version of the time "right now". + /*!< this was formerly known as a very useful method called 'utility::timestamp'. + that naming was fairly abusive to keep straight from the time_stamp class, so the + functionality has been absorbed. */ + + // standard operators: keep in mind that time is represented by an ever + // increasing number. so, if a value A is less than a value B, that means + // that A is older than B, since it occurred at an earlier time. + virtual bool less_than(const basis::orderable &that) const { + const time_stamp *cast = dynamic_cast(&that); + if (!cast) return false; + return c_stamp < cast->c_stamp; + } + + virtual bool equal_to(const basis::equalizable &that) const { + const time_stamp *cast = dynamic_cast(&that); + if (!cast) return false; + return c_stamp == cast->c_stamp; + } + + // helper functions for using the OS's time support. + + static timeval fill_timeval_ms(int milliseconds); + //!< returns a timeval system object that represents the "milliseconds". + /*!< if "milliseconds" is zero, then the returned timeval will + represent zero time passing (rather than infinite duration as some + functions assume). */ + +private: + time_representation c_stamp; //!< the low-level time stamp is held here. + + void fill_in_time(); //!< stuffs the current time into the held value. + + static time_representation get_time_now(); + //!< platform specific function that gets uptime. + + static rollover_record &rollover_rover(); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/timely/timer_driver.cpp b/nucleus/library/timely/timer_driver.cpp new file mode 100644 index 00000000..9be8aba0 --- /dev/null +++ b/nucleus/library/timely/timer_driver.cpp @@ -0,0 +1,453 @@ +/* +* Name : timer_driver +* Author : Chris Koeritz + +* Copyright (c) 2003-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "timer_driver.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef __UNIX__ + #include +#endif + +using namespace basis; +using namespace processes; +using namespace structures; +using namespace timely; + +//#define DEBUG_TIMER_DRIVER + // uncomment for noisy code. + +#undef LOG +#define LOG(tpr) printf( (time_stamp::notarize() + "timer_driver::" + func + tpr).s() ) + +namespace timely { + +const int INITIAL_TIMER_GRANULARITY = 14; + // the timer will support durations of this length or greater initially. + // later durations will be computed based on the timers waiting. + +const int MAX_TIMER_PREDICTION = 140; + // this is the maximum predictive delay before we wake up again to see if + // any new timed items have arrived. this helps us to not wait too long + // when something's scheduled in between snoozes. + +const int PAUSE_TIME = 200; + // we will pause this many milliseconds if the timer is already occurring + // when we're trying to get the lock on our list. + +const int LONG_TIME = 1 * HOUR_ms; + // the hook can be postponed a really long time with this when necessary. + +////////////// + +SAFE_STATIC(timer_driver, timer_driver::global_timer_driver, ) + +////////////// + +#ifdef __UNIX__ +const int OUR_SIGNAL = SIGUSR2; + +class signalling_thread : public ethread +{ +public: + signalling_thread(int initial_interval) : ethread(initial_interval) {} + + void perform_activity(void *formal(ptr)) { + raise(OUR_SIGNAL); + } + +private: +}; +#endif + +#ifdef __UNIX__ +void timer_driver_private_handler(int signal_seen) +#elif defined(__WIN32__) +void __stdcall timer_driver_private_handler(window_handle hwnd, basis::un_int msg, + UINT_PTR id, un_long time) +#else + #error No timer method known for this OS. +#endif +{ +#ifdef DEBUG_TIMER_DRIVER + #undef static_class_name + #define static_class_name() "timer_driver" + FUNCDEF("timer_driver_private_handler"); +#endif +#ifdef __UNIX__ + int seen = signal_seen; + if (seen != OUR_SIGNAL) { +#elif defined(__WIN32__) + basis::un_int *seen = (basis::un_int *)id; + if (seen != program_wide_timer().real_timer_id()) { +#else + if (true) { // unknown OS. +#endif +#ifdef DEBUG_TIMER_DRIVER + LOG(a_sprintf("unknown signal/message %x caught.", (void *)seen)); +#endif + return; + } + program_wide_timer().handle_system_timer(); + #undef static_class_name +} + +////////////// + +class driven_object_record +{ +public: + int _duration; // the interval for timer hits on this object. + timeable *_to_invoke; // the object that will be called back. + time_stamp _next_hit; // next time the timer should hit for this object. + bool _okay_to_invoke; // true if this object is okay to call timers on. + bool _handling_timer; // true if we're handling this object right now. + + driven_object_record(int duration, timeable *to_invoke) + : _duration(duration), _to_invoke(to_invoke), _next_hit(duration), + _okay_to_invoke(true), _handling_timer(false) {} +}; + +class driven_objects_list +: public amorph, + public virtual root_object +{ +public: + DEFINE_CLASS_NAME("driven_objects_list"); + + int find_obj(timeable *obj) { + for (int i = 0; i < elements(); i++) { + if (borrow(i) && (borrow(i)->_to_invoke == obj)) + return i; + } + return common::NOT_FOUND; + } +}; + +////////////// + +timer_driver::timer_driver() +: _timers(new driven_objects_list), + _lock(new mutex), +#ifdef __UNIX__ + _prompter(new signalling_thread(INITIAL_TIMER_GRANULARITY)), +#endif +#ifdef __WIN32__ + _real_timer_id(NIL), +#endif + _in_timer(false) +{ + hookup_OS_timer(INITIAL_TIMER_GRANULARITY); + +#ifdef __UNIX__ + // register for the our personal signal. + signal(OUR_SIGNAL, &timer_driver_private_handler); + _prompter->start(NIL); +#endif +} + +timer_driver::~timer_driver() +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("destructor"); +#endif +#ifdef __UNIX__ + _prompter->stop(); + + struct sigaction action; + action.sa_handler = SIG_DFL; + action.sa_sigaction = NIL; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; +#ifndef __APPLE__ + action.sa_restorer = NIL; +#endif + int ret = sigaction(OUR_SIGNAL, &action, NIL); + if (ret) { +///uhhh + } +#endif + unhook_OS_timer(); + + // make sure we aren't still in a timer handler when we reset our list. + while (true) { + _lock->lock(); + if (_in_timer) { + _lock->unlock(); +#ifdef DEBUG_TIMER_DRIVER + LOG("waiting to acquire timer_driver lock."); +#endif + time_control::sleep_ms(PAUSE_TIME); + } else { + break; + } + } + + _timers->reset(); // clear out the registered functions. + _lock->unlock(); + + WHACK(_timers); + WHACK(_lock); +#ifdef __UNIX__ + WHACK(_prompter); +#endif + +#ifdef DEBUG_TIMER_DRIVER + LOG("timer_driver is closing down."); +#endif +} + +#ifdef __WIN32__ +basis::un_int *timer_driver::real_timer_id() { return _real_timer_id; } +#endif + +bool timer_driver::zap_timer(timeable *to_remove) +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("zap_timer"); +#endif +#ifdef DEBUG_TIMER_DRIVER + if (_in_timer) { + LOG("hmmm: zapping timer while handling previous timer...!"); + } +#endif + auto_synchronizer l(*_lock); + int indy = _timers->find_obj(to_remove); + if (negative(indy)) return false; // unknown. +#ifdef DEBUG_TIMER_DRIVER + LOG(a_sprintf("zapping timer %x.", to_remove)); +#endif + driven_object_record *reco = _timers->borrow(indy); + reco->_okay_to_invoke = false; + if (reco->_handling_timer) { + // results are not guaranteed if we see this situation. +#ifdef DEBUG_TIMER_DRIVER + LOG(a_sprintf("Logic Error: timer %x being zapped WHILE BEING HANDLED!", + to_remove)); +#endif + } + return true; +} + +bool timer_driver::set_timer(int duration, timeable *to_invoke) +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("set_timer"); + if (_in_timer) { + LOG("hmmm: setting timer while handling previous timer...!"); + } +#endif +#ifdef DEBUG_TIMER_DRIVER + LOG(a_sprintf("setting timer %x to %d ms.", to_invoke, duration)); +#endif + auto_synchronizer l(*_lock); + // find any existing record. + int indy = _timers->find_obj(to_invoke); + if (negative(indy)) { + // add a new record to list. + _timers->append(new driven_object_record(duration, to_invoke)); + } else { + // change the existing record. + driven_object_record *reco = _timers->borrow(indy); + reco->_duration = duration; + reco->_okay_to_invoke = true; // just in case. + } + return true; +} + +void timer_driver::handle_system_timer() +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("handle_system_timer"); +#endif + if (_in_timer) { +#ifdef DEBUG_TIMER_DRIVER + LOG("terrible error: invoked system timer while handling previous timer."); +#endif + return; + } + unhook_OS_timer(); + +#ifdef DEBUG_TIMER_DRIVER + LOG("into handling OS timer..."); +#endif + + array to_invoke_now; + + { + // lock the list for a short time, just to put in a stake for the timer + // flag; no one is allowed to change the list while this is set to true. + auto_synchronizer l(*_lock); + _in_timer = true; + + // zip across our list and find out which of the timer functions should be + // invoked. + for (int i = 0; i < _timers->elements(); i++) { + driven_object_record *funky = _timers->borrow(i); + if (!funky) { + const char *msg = "error: timer_driver's timer list logic is broken."; +#ifdef DEBUG_TIMER_DRIVER + LOG(msg); +#endif +#ifdef CATCH_ERRORS + throw msg; +#endif + _timers->zap(i, i); + i--; // skip back over dud record. + continue; + } + if (funky->_next_hit <= time_stamp()) { + // this one needs to be jangled. + to_invoke_now += funky; + } + } + } + +#ifdef DEBUG_TIMER_DRIVER + astring pointer_dump; + for (int i = 0; i < to_invoke_now.length(); i++) { + driven_object_record *funky = to_invoke_now[i]; + pointer_dump += a_sprintf("%x ", funky->_to_invoke); + } + if (pointer_dump.t()) + LOG(astring("activating ") + pointer_dump); +#endif + + // now that we have a list of timer functions, let's call on them. + for (int i = 0; i < to_invoke_now.length(); i++) { + driven_object_record *funky = to_invoke_now[i]; + { + auto_synchronizer l(*_lock); + if (!funky->_okay_to_invoke) continue; // skip this guy. + funky->_handling_timer = true; + } + // call the timer function. + funky->_to_invoke->handle_timer_callback(); + { + auto_synchronizer l(*_lock); + funky->_handling_timer = false; + } + // reset the time for the next hit. + funky->_next_hit.reset(funky->_duration); + } + + // compute the smallest duration before the next guy should fire. + int next_timer_duration = MAX_TIMER_PREDICTION; + time_stamp now; // pick a point in time as reference for all timers. + for (int i = 0; i < _timers->elements(); i++) { + driven_object_record *funky = _timers->borrow(i); + int funky_time = int(funky->_next_hit.value() - now.value()); + // we limit the granularity of timing since we don't want to be raging + // on the CPU with too small a duration. + if (funky_time < INITIAL_TIMER_GRANULARITY) + funky_time = INITIAL_TIMER_GRANULARITY; + if (funky_time < next_timer_duration) + next_timer_duration = funky_time; + } + + { + // release the timer flag again and do any cleanups that are necessary. + auto_synchronizer l(*_lock); + _in_timer = false; + for (int i = 0; i < _timers->elements(); i++) { + driven_object_record *funky = _timers->borrow(i); + if (!funky->_okay_to_invoke) { + // clean up something that was unhooked. + _timers->zap(i, i); + i--; + } + } + } + +#ifdef DEBUG_TIMER_DRIVER + LOG("done handling OS timer."); +#endif + + // set the next expiration time to the smallest next guy. + reset_OS_timer(next_timer_duration); +} + +// the following OS_timer methods do not need to lock the mutex, since they +// are not actually touching the list of timers. + +void timer_driver::hookup_OS_timer(int duration) +{ + FUNCDEF("hookup_OS_timer"); + if (negative(duration)) { +#ifdef DEBUG_TIMER_DRIVER + LOG("seeing negative duration for timer!"); +#endif + duration = 1; + } else if (!duration) { +#ifdef DEBUG_TIMER_DRIVER + LOG("patching zero duration for timer."); +#endif + duration = 1; + } +#ifdef DEBUG_TIMER_DRIVER + LOG(a_sprintf("hooking next OS timer in %d ms.", duration)); +#endif +#ifdef __UNIX__ + // just make our thread hit after the duration specified. + _prompter->reschedule(duration); +#elif defined(__WIN32__) + int max_tries_left = 100; + while (max_tries_left-- >= 0) { + _real_timer_id = (basis::un_int *)SetTimer(NIL, 0, duration, + timer_driver_private_handler); + if (!_real_timer_id) { + // failure to set the timer. + LOG("could not set the interval timer."); + time_control::sleep_ms(50); // snooze for a bit to see if we can get right. + continue; + } else + break; // success hooking timer. + } +#endif +} + +void timer_driver::unhook_OS_timer() +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("unhook_OS_timer"); +#endif +#ifdef __UNIX__ + // postpone the thread for quite a while so we can take care of business. + _prompter->reschedule(LONG_TIME); +#elif defined(__WIN32__) + if (_real_timer_id) KillTimer(NIL, (UINT_PTR)_real_timer_id); +#endif +#ifdef DEBUG_TIMER_DRIVER + LOG("unhooked OS timer."); +#endif +} + +void timer_driver::reset_OS_timer(int next_hit) +{ +#ifdef DEBUG_TIMER_DRIVER + FUNCDEF("reset_OS_timer"); +#endif + unhook_OS_timer(); // stop the timer from running. + hookup_OS_timer(next_hit); // restart the timer with the new interval. +} + +} //namespace. + diff --git a/nucleus/library/timely/timer_driver.h b/nucleus/library/timely/timer_driver.h new file mode 100644 index 00000000..ae2b9735 --- /dev/null +++ b/nucleus/library/timely/timer_driver.h @@ -0,0 +1,115 @@ +#ifndef TIMER_DRIVER_CLASS +#define TIMER_DRIVER_CLASS + +/*****************************************************************************\ +* * +* Name : timer_driver * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2003-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace timely { + +// forward. +class driven_objects_list; +class signalling_thread; + +////////////// + +//! timeable is the base for objects that can be hooked into timer events. + +class timeable : public virtual basis::root_object +{ +public: +//// virtual ~timeable() {} + virtual void handle_timer_callback() = 0; + //!< this method is invoked when the timer period elapses for this object. +}; + +////////////// + +//! Provides platform-independent timer support. +/*! + Multiple objects can be hooked to the timer to be called when their interval + elapses. The driver allows new timeables to be added as needed. + + NOTE: Only one of the timer_driver objects is allowed per program. +*/ + +class timer_driver : public virtual basis::root_object +{ +public: + timer_driver(); + virtual ~timer_driver(); + + DEFINE_CLASS_NAME("timer_driver"); + + // main methods for controlling timeables. + + bool set_timer(int duration, timeable *to_invoke); + //!< sets a timer to call "to_invoke" every "duration" milliseconds. + /*!< if the object "to_invoke" already exists, then its duration is + changed. */ + + bool zap_timer(timeable *to_drop); + //!< removes the timer that was established for "to_drop". + /*!< do not zap a timer from its own callback! that could cause + synchronization problems. */ + + // internal methods. + +#ifdef __WIN32__ + basis::un_int *real_timer_id(); + //!< provides the timer id for comparison on windows platforms. +#endif + + void handle_system_timer(); + //!< invoked by the OS timer support and must be called by main thread. + + static timer_driver &global_timer_driver(); + //!< the first time this is invoked, it creates a program-wide timer driver. + +private: + driven_objects_list *_timers; //!< timer hooked objects. + basis::mutex *_lock; //!< protects list of timers. +#ifdef __UNIX__ + signalling_thread *_prompter; //!< drives our timers. +#endif +#ifdef __WIN32__ + basis::un_int *_real_timer_id; //!< used for storing window timer handle. +#endif + bool _in_timer; //!< true if we're handling the timer right now. + + // these do the low-level system magic required to get a function hooked + // to a timer. + void hookup_OS_timer(int duration); + //!< hooks us into the timer events at the "duration" specified. + void reset_OS_timer(int next_hit); + //!< changes the root interval to "next_hit". + /*!< only that many milliseconds will elapse before the next timer hit. */ + void unhook_OS_timer(); + //!< disconnects us from the timer events. +}; + +////////////// + +#define program_wide_timer() timer_driver::global_timer_driver() + //!< provides access to the singleton timer_driver. + /*!< no other timer_driver objects should ever be created, since this + single one will service all timer needs within the program. */ + +} //namespace. + +#endif + diff --git a/nucleus/library/unit_test/makefile b/nucleus/library/unit_test/makefile new file mode 100644 index 00000000..7529795f --- /dev/null +++ b/nucleus/library/unit_test/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = unit_test +TYPE = library +SOURCE = unit_base.cpp +TARGETS = unit_test.lib + +include cpp/rules.def + diff --git a/nucleus/library/unit_test/unit_base.cpp b/nucleus/library/unit_test/unit_base.cpp new file mode 100644 index 00000000..439cc697 --- /dev/null +++ b/nucleus/library/unit_test/unit_base.cpp @@ -0,0 +1,395 @@ +/* +* Name : unit test tools +* Author : Chris Koeritz +** +* Copyright (c) 2009-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +#include "unit_base.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; + +#define BASE_LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s) + +namespace unit_test { + +const int EXPECTED_MAXIMUM_TESTS = 10008; //!< maximum number of tests expected. + +const char *name_for_bools(bool bv) +{ if (bv) return "true"; else return "false"; } + +unit_base::unit_base() +: c_lock(), + c_total_tests(0), + c_passed_tests(0), + c_successful(EXPECTED_MAXIMUM_TESTS), + c_failed(EXPECTED_MAXIMUM_TESTS) +{ +} + +unit_base::~unit_base() {} + +int unit_base::total_tests() const { return c_total_tests; } + +int unit_base::passed_tests() const { return c_passed_tests; } + +int unit_base::failed_tests() const +{ return c_total_tests - c_passed_tests; } + +void unit_base::count_successful_test(const basis::astring &class_name, const basis::astring &test_name) +{ + auto_synchronizer synch(c_lock); + outcome ret = c_successful.add(class_name + " -- " + test_name, ""); + if (ret == common::IS_NEW) { + c_total_tests++; + c_passed_tests++; + } +} + +void unit_base::record_pass(const basis::astring &class_name, const astring &test_name, + const astring &diag) +{ + auto_synchronizer synch(c_lock); + count_successful_test(class_name, test_name); +//hmmm: kind of lame bailout on printing this. +// it gets very very wordy if it's left in. +#ifdef DEBUG + astring message = astring("OKAY: ") + class_name + " in test [" + test_name + "]"; + BASE_LOG(message); +#endif +} + +void unit_base::count_failed_test(const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diag) +{ + auto_synchronizer synch(c_lock); + outcome ret = c_failed.add(class_name + " -- " + test_name, diag); + if (ret == common::IS_NEW) { + c_total_tests++; + } +} + +void unit_base::record_fail(const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + count_failed_test(class_name, test_name, diag); + astring message = astring("\nFAIL: ") + class_name + " in test [" + test_name + "]\n" + diag + "\n"; + BASE_LOG(message); +} + +void unit_base::record_successful_assertion(const basis::astring &class_name, const astring &test_name, + const astring &assertion_name) +{ + record_pass(class_name, test_name, + astring("no problem with: ") + assertion_name); +} + +void unit_base::record_failed_object_compare(const hoople_standard &a, const hoople_standard &b, + const basis::astring &class_name, const astring &test_name, const astring &assertion_name) +{ + astring a_state, b_state; + a.text_form(a_state); + b.text_form(b_state); + record_fail(class_name, test_name, + astring("Error in assertion ") + assertion_name + ":\n" + + "==============\n" + + a_state + "\n" + + "=== versus ===\n" + + b_state + "\n" + + "=============="); +} + +void unit_base::record_failed_int_compare(int a, int b, + const basis::astring &class_name, const astring &test_name, const astring &assertion_name) +{ + record_fail(class_name, test_name, + astring("Error in assertion ") + assertion_name + + a_sprintf(": inappropriate values: %d & %d", a, b)); +} + +void unit_base::record_failed_double_compare(double a, double b, + const basis::astring &class_name, const astring &test_name, const astring &assertion_name) +{ + record_fail(class_name, test_name, + astring("Error in assertion ") + assertion_name + + a_sprintf(": inappropriate values: %f & %f", a, b)); +} + +void unit_base::record_failed_pointer_compare(const void *a, const void *b, + const basis::astring &class_name, const astring &test_name, const astring &assertion_name) +{ + record_fail(class_name, test_name, + astring("Error in assertion ") + assertion_name + + a_sprintf(": inappropriate values: %p & %p", a, b)); +} + +void unit_base::record_failed_tf_assertion(bool result, + bool expected_result, const basis::astring &class_name, const astring &test_name, + const astring &assertion_name) +{ + record_fail(class_name, test_name, astring("Error in assertion ") + assertion_name + + ": expected " + name_for_bools(expected_result) + + " but found " + name_for_bools(result)); +} + +void unit_base::assert_equal(const hoople_standard &a, const hoople_standard &b, + const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_equal"); + bool are_equal = (a == b); + if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_object_compare(a, b, class_name, test_name, func); +} + +void unit_base::assert_not_equal(const hoople_standard &a, const hoople_standard &b, + const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_not_equal"); + bool are_equal = (a == b); + if (!are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_object_compare(a, b, class_name, test_name, func); +} + +const char *byte_array_phrase = "byte_array[%d]"; // for printing a text form of byte_array. + +void unit_base::assert_equal(const basis::byte_array &a, const basis::byte_array &b, + const basis::astring &class_name, const basis::astring &test_name, const astring &diag) +{ + FUNCDEF("assert_equal"); + bool are_equal = (a == b); + if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else { + astring a_s = a_sprintf(byte_array_phrase, a.length()); + astring b_s = a_sprintf(byte_array_phrase, b.length()); + record_failed_object_compare(a_s, b_s, class_name, test_name, func); + } +} + +void unit_base::assert_not_equal(const basis::byte_array &a, const basis::byte_array &b, + const basis::astring &class_name, const basis::astring &test_name, const astring &diag) +{ + FUNCDEF("assert_not_equal"); + bool are_equal = (a == b); + if (!are_equal) record_successful_assertion(class_name, test_name, func); + else { + astring a_s = a_sprintf(byte_array_phrase, a.length()); + astring b_s = a_sprintf(byte_array_phrase, b.length()); + record_failed_object_compare(a_s, b_s, class_name, test_name, func); + } +} + +void unit_base::assert_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_equal integer"); + bool are_equal = a == b; + if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_not_equal(int a, int b, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_not_equal integer"); + bool are_inequal = a != b; + if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_int_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_equal double"); + bool are_equal = a == b; + if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_not_equal(double a, double b, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_not_equal double"); + bool are_inequal = a != b; + if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_double_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + + +void unit_base::assert_equal(const void *a, const void *b, + const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_equal void pointer"); + bool are_equal = a == b; + if (are_equal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_not_equal(const void *a, const void *b, + const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_not_equal void pointer"); + bool are_inequal = a != b; + if (are_inequal) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_pointer_compare(a, b, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_true(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_true"); + if (result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_tf_assertion(result, true, class_name, test_name, astring(func) + ": " + diag); +} + +void unit_base::assert_false(bool result, const basis::astring &class_name, const astring &test_name, const astring &diag) +{ + FUNCDEF("assert_false"); + if (!result) record_successful_assertion(class_name, test_name, astring(func) + ": " + diag); + else record_failed_tf_assertion(result, false, class_name, test_name, astring(func) + ": " + diag); +} + +int unit_base::final_report() +{ + auto_synchronizer synch(c_lock); + int to_return = 0; // return success until we know otherwise. + + astring keyword = "FAILURE"; // but be pessimistic about overall result at first..? + + // check whether we really did succeed or not. + if (c_total_tests == c_passed_tests) keyword = "SUCCESS"; // success! + else to_return = 12; // a failure return. + + if (!c_total_tests) keyword = "LAMENESS (no tests!)"; // boring! + +// astring message = keyword + " for " +// + application_configuration::application_name() +// + a_sprintf(": %d of %d atomic tests passed.", +// c_passed_tests, c_total_tests); +// BASE_LOG(message); + + astring message = keyword + " for " + + filename(application_configuration::application_name()).basename().raw() + + a_sprintf(": %d of %d unit tests passed.", + c_successful.symbols(), c_successful.symbols() + c_failed.symbols()); + BASE_LOG(message); + + // send an xml file out for the build engine to analyze. + write_cppunit_xml(); + + return to_return; +} + +void unit_base::write_cppunit_xml() +{ + auto_synchronizer synch(c_lock); + astring logs_dir = environment::get("LOGS_DIR"); + if (logs_dir == astring::empty_string()) logs_dir = "logs"; // uhhh. + astring outfile = logs_dir + "/" + + filename(application_configuration::application_name()).basename().raw() + + ".xml"; + +//BASE_LOG(astring("outfile is ") + outfile); + + int id = 1; + + xml_generator report; + string_table attribs; + + // we are emulating a cppunit xml output. + + report.open_tag("TestRun"); + + report.open_tag("FailedTests"); + for (int i = 0; i < c_failed.symbols(); i++) { + attribs.reset(); + attribs.add("id", a_sprintf("%d", id++)); + report.open_tag("FailedTest", attribs); + attribs.reset(); +//hmmm: does open_tag eat the attribs? we could stop worrying about resetting. + report.open_tag("Name"); + report.add_content(c_failed.name(i)); + report.close_tag("Name"); + + report.open_tag("FailureType", attribs); + report.add_content("Assertion"); + report.close_tag("FailureType"); + + report.open_tag("Location", attribs); + + report.open_tag("File", attribs); + report.add_content(application_configuration::application_name()); + report.close_tag("File"); + + report.open_tag("Line", attribs); + report.add_content("0"); + report.close_tag("Line"); + + report.close_tag("Location"); + + report.open_tag("Message"); + report.add_content(c_failed[i]); + report.close_tag("Message"); + + report.close_tag("FailedTest"); + } + report.close_tag("FailedTests"); + + report.open_tag("SuccessfulTests"); + for (int i = 0; i < c_successful.symbols(); i++) { + attribs.reset(); + attribs.add("id", a_sprintf("%d", id++)); + attribs.reset(); + report.open_tag("Test", attribs); + report.open_tag("Name"); + report.add_content(c_successful.name(i)); + report.close_tag("Name"); + report.close_tag("Test"); + } + report.close_tag("SuccessfulTests"); + + report.open_tag("Statistics"); + report.open_tag("Tests"); + report.add_content(a_sprintf("%d", c_failed.symbols() + c_successful.symbols())); + report.close_tag("Tests"); + + report.open_tag("FailuresTotal"); + report.add_content(a_sprintf("%d", c_failed.symbols())); + report.close_tag("FailuresTotal"); + + report.open_tag("Errors"); + report.add_content("0"); + report.close_tag("Errors"); + + report.open_tag("Failures"); + report.add_content(a_sprintf("%d", c_failed.symbols())); + report.close_tag("Failures"); + + report.close_tag("Statistics"); + + report.close_tag("TestRun"); + + astring text_report = report.generate(); +// BASE_LOG(astring("got report\n") + text_report); + + byte_filer xml_out(outfile, "wb"); + xml_out.write(text_report); + xml_out.close(); +} + +} //namespace. + diff --git a/nucleus/library/unit_test/unit_base.h b/nucleus/library/unit_test/unit_base.h new file mode 100644 index 00000000..d5185dec --- /dev/null +++ b/nucleus/library/unit_test/unit_base.h @@ -0,0 +1,186 @@ +#ifndef UNIT_BASE_GROUP +#define UNIT_BASE_GROUP + +/* +* Name : unit_base tools for unit testing +* Author : Chris Koeritz +** +* Copyright (c) 2009-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +*/ + +//! Useful support functions for unit testing, especially within hoople. +/*! + Provides a simple framework for unit testing the hoople classes. +*/ + +#include +#include +#include +#include +#include + +namespace unit_test { + +// these macros can be used to put more information into the test name. +// using these is preferable to calling the class methods directly, since +// those cannot print out the original version of the formal parameters a +// and b for the test; the macros show the expressions that failed rather +// than just the values. +// note that these require the calling object to be derived from unit_base. +#define UNIT_BASE_THIS_OBJECT (*this) + // the macro for UNIT_BASE_THIS_OBJECT allows tests to use a single unit_base object by + // changing the value of UNIT_BASE_THIS_OBJECT to refer to the proper object's name. +#define ASSERT_EQUAL(a, b, test_name) { \ + BASE_FUNCTION(func); \ + UNIT_BASE_THIS_OBJECT.assert_equal(a, b, function_name, test_name, basis::astring(#a) + " must be equal to " + #b); \ +} +#define ASSERT_INEQUAL(a, b, test_name) { \ + BASE_FUNCTION(func); \ + UNIT_BASE_THIS_OBJECT.assert_not_equal(a, b, function_name, test_name, basis::astring(#a) + " must be inequal to " + #b); \ +} +#define ASSERT_TRUE(a, test_name) { \ + BASE_FUNCTION(func); \ + UNIT_BASE_THIS_OBJECT.assert_true(a, function_name, test_name, basis::astring(#a) + " must be true"); \ +} +#define ASSERT_FALSE(a, test_name) { \ + BASE_FUNCTION(func); \ + UNIT_BASE_THIS_OBJECT.assert_false(a, function_name, test_name, basis::astring(#a) + " must be false"); \ +} +// pointer versions for nicer syntax. +#define ASSERT_NULL(x, y) ASSERT_FALSE(x, y) +#define ASSERT_NON_NULL(x, y) ASSERT_TRUE(x, y) + +class unit_base : public virtual basis::nameable +{ +public: + unit_base(); + virtual ~unit_base(); + + DEFINE_CLASS_NAME("unit_base"); + + int total_tests() const; //!< the total count of tests that have been run. + int passed_tests() const; //!< count of successful tests run. + int failed_tests() const; //!< count of number of failed tests. + + void assert_equal(const basis::hoople_standard &a, const basis::hoople_standard &b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that the objects a and b are equal. + void assert_not_equal(const basis::hoople_standard &a, const basis::hoople_standard &b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that objects a and b are NOT equal. + + void assert_equal(const basis::byte_array &a, const basis::byte_array &b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that the byte_arrays a and b are equal. + void assert_not_equal(const basis::byte_array &a, const basis::byte_array &b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that the byte_arrays a and b are not equal. + + void assert_equal(int a, int b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that integers a and b are equal. + void assert_not_equal(int a, int b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that integers a and b are NOT equal. + + void assert_equal(double a, double b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that doubles a and b are equal. + void assert_not_equal(double a, double b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that doubles a and b are NOT equal. + + void assert_equal(const void *a, const void *b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that void pointers a and b are equal. + /*!< note that you can use this to compare any two pointers as long as + you cast them to (void *) first. this reports whether the two have the + same address in memory, but nothing about their contents. */ + void assert_not_equal(const void *a, const void *b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that void pointers a and b are NOT equal. + + void assert_true(bool result, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that the "result" is a true boolean. + void assert_false(bool result, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< tests that the "result" is a false boolean. + + // these two methods can be used in an ad hoc manner when using the above + // assert methods is not helpful. the "test_name" should be provided + // like above, but the "diagnostic_info" can be phrased in any way needed + // to describe the pass or fail event. + void record_pass(const basis::astring &class_name, + const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< very general recording of a successful test; better to use asserts. + void record_fail(const basis::astring &class_name, + const basis::astring &test_name, + const basis::astring &diagnostic_info); + //!< very general recording of a failed test; better to use asserts. + + int final_report(); + //!< generates a report of the total number of tests that succeeded. + /*!< the return value of this can be used as the exit value of the overall test. if there + are any failures, then a failure result is returned (non-zero). otherwise the successful + exit status of zero is returned. */ + +private: + basis::mutex c_lock; //!< protects our objects for concurrent access. + int c_total_tests; //!< how many tests have been run? + int c_passed_tests; //!< how many of those passed? + structures::string_table c_successful; //!< successful test names. + structures::string_table c_failed; //!< failing test names. + + void write_cppunit_xml(); + //!< outputs a report file in cppunit format so CI engines can see results. + + void count_successful_test(const basis::astring &class_name, const basis::astring &test_name); + //!< records one successful test. + + void count_failed_test(const basis::astring &class_name, const basis::astring &test_name, const basis::astring &diag); + //!< records one failed test. + + void record_successful_assertion(const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &assertion_name); + //!< used by happy tests to record their success. + + void record_failed_object_compare(const basis::hoople_standard &a, + const basis::hoople_standard &b, const basis::astring &class_name, + const basis::astring &test_name, const basis::astring &assertion_name); + void record_failed_int_compare(int a, int b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &assertion_name); + void record_failed_double_compare(double a, double b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &assertion_name); + void record_failed_tf_assertion(bool result, bool expected_result, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &assertion_name); + void record_failed_pointer_compare(const void *a, const void *b, + const basis::astring &class_name, const basis::astring &test_name, + const basis::astring &assertion_name); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/versions/makefile b/nucleus/library/versions/makefile new file mode 100644 index 00000000..0eea688c --- /dev/null +++ b/nucleus/library/versions/makefile @@ -0,0 +1,9 @@ +include cpp/variables.def + +PROJECT = versions +TYPE = library +SOURCE = version_checker.cpp version_ini.cpp +TARGETS = versions.lib + +include cpp/rules.def + diff --git a/nucleus/library/versions/version.ini.example b/nucleus/library/versions/version.ini.example new file mode 100644 index 00000000..3fc5e56a --- /dev/null +++ b/nucleus/library/versions/version.ini.example @@ -0,0 +1,19 @@ +# This is an example "version.ini" file for a library or application. +# Using this file and the "main_ver.ini" (see "proto_main_ver.ini" for more +# information), the version support creates the version record in a resource +# file and outputs the header for version checking (libraries only). + +[version] + ; currently, the only section name used is "version". +description=Gurp Library: assorted multi-purpose frotzing components + ; longish text summary of the library's purpose. +name=Gurp_Library + ; the name that the library goes by. how it thinks of itself. +root=gurp + ; the root name for the project's dll or exe file. don't include the + ; extension; that's specified below. +extension=dll + ; type of file created by the project. the default is "dll", which indicates + ; that a version checking header should be created (called "version.h"). + ; the other option is "exe", which doesn't bother with the version header. + diff --git a/nucleus/library/versions/version_checker.cpp b/nucleus/library/versions/version_checker.cpp new file mode 100644 index 00000000..24d06b69 --- /dev/null +++ b/nucleus/library/versions/version_checker.cpp @@ -0,0 +1,372 @@ +/*****************************************************************************\ +* * +* Name : check_version * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "version_checker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace basis; +using namespace configuration; +using namespace loggers; +using namespace structures; + +#ifndef BOOT_STRAPPING + // pull in the version specified for this build. +///hmmm: on hold! #include <__build_version.h> +#else + // plug in a fake version for our bootstrapping process. + #define __build_FILE_VERSION "108.420.1024.10008" +#endif + +#ifdef _MSC_VER + #include + #include +#endif + +#ifdef __WIN32__ + // ensures that we handle the data properly regardless of unicode settings. + #ifdef UNICODE + #define render_ptr(ptr) from_unicode_temp( (UTF16 *) ptr) + #else + #define render_ptr(ptr) ( (char *) ptr) + #endif +#endif + +////////////// + +namespace versions { + +version_checker::version_checker(const astring &library_file_name, + const version &expected_version, const astring &version_complaint) +: _library_file_name(new astring(library_file_name)), + _expected_version(new version(expected_version)), + _version_complaint(new astring(version_complaint)) +{} + +version_checker::~version_checker() +{ + WHACK(_library_file_name); + WHACK(_expected_version); + WHACK(_version_complaint); +} + +astring version_checker::text_form() const +{ + return astring(class_name()) + ": library_file_name=" + *_library_file_name + + ", expected_version=" + _expected_version->text_form() + + ", complaint_message=" + *_version_complaint; +} + +bool version_checker::good_version() const +{ + astring version_disabler = environment::get("TMP"); + version_disabler += "/no_version_check.txt"; + FILE *always_okay = fopen(version_disabler.s(), "r"); + if (always_okay) { + fclose(always_okay); + return true; + } + + version version_found = retrieve_version(*_library_file_name); + if (version_found.compatible(*_expected_version)) return true; // success. + complain_wrong_version(*_library_file_name, *_expected_version, + version_found); + return false; +} + +bool version_checker::loaded(const astring &library_file_name) +{ +#ifdef __WIN32__ + return bool(get_handle(library_file_name) != 0); +#else +//temp code. + return true || library_file_name.t(); +#endif +} + +void *version_checker::get_handle(const astring &library_file_name) +{ +#ifdef __WIN32__ + return GetModuleHandle(to_unicode_temp(library_file_name)); +#else +//hmmm: there really isn't this concept on OSes that i'm aware of. + return 0 && library_file_name.t(); +#endif +} + +astring version_checker::module_name(const void *module_handle) +{ +#ifdef __UNIX__ +//hmmm: implement module name for linux if that makes sense. + if (module_handle) {} + return application_configuration::application_name(); +#elif defined(__WIN32__) + flexichar low_buff[MAX_ABS_PATH + 1]; + GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1); + astring buff = from_unicode_temp(low_buff); + buff.to_lower(); + return buff; +#else + #pragma message("module_name unknown for this operating system.") + return application_name(); +#endif +} + +astring version_checker::get_name(const void *to_find) +{ return module_name(to_find); } + +bool version_checker::retrieve_version_info(const astring &filename, + byte_array &to_fill) +{ + to_fill.reset(); // clear the buffer. + + // determine the required size of the version info buffer. + int required_size; +#ifdef __WIN32__ + un_long module_handle; // filled with the dll or exe handle. + required_size = GetFileVersionInfoSize(to_unicode_temp(filename), &module_handle); +#else + required_size = 0 && filename.t(); +#endif + if (!required_size) return false; + to_fill.reset(required_size); // resize the buffer. + + // read the version info into our buffer. + bool success = false; +#ifdef __WIN32__ + success = GetFileVersionInfo(to_unicode_temp(filename), module_handle, + required_size, to_fill.access()); +#else + success = false; +#endif + return success; +} + +bool version_checker::get_language(byte_array &version_chunk, + basis::un_short &high, basis::un_short &low) +{ + high = 0; + low = 0; +#ifdef __WIN32__ + // determine the language that the version's written in. + basis::un_int data_size; + void *pointer_to_language_structure; + // query the information from the version blob. + if (!VerQueryValue(version_chunk.access(), + to_unicode_temp("\\VarFileInfo\\Translation"), + &pointer_to_language_structure, &data_size)) + return false; + // get the low & high shorts of the structure. + high = LOWORD(*(unsigned int *)pointer_to_language_structure); + low = HIWORD(*(unsigned int *)pointer_to_language_structure); +#else + high = 0 && version_chunk.length(); + low = 0; +#endif + + return true; +} + +version version_checker::retrieve_version(const astring &filename) +{ +#ifdef UNIX + + // totally bogus stand-in; this just returns the version we were built with + // rather than the version that's actually tagged on the file. + +//hmmm: fix this! get the version header back. +// return version(__build_FILE_VERSION); + return version(); + +#endif + + byte_array version_info_found(0, NIL); + if (!retrieve_version_info(filename, version_info_found)) + return version(0, 0, 0, 0); + + basis::un_short high, low; // holds the language of the version data. + if (!get_language(version_info_found, high, low)) + return version(0, 0, 0, 0); + + // retrieve the file version from version info using the appropriate + // language. + astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low); + astring file_version_key(root_key + astring("\\FileVersion")); + + astring version_string; +#ifdef __WIN32__ + abyte *file_version_pointer; + basis::un_int data_size; + if (!VerQueryValue(version_info_found.access(), + to_unicode_temp(file_version_key), + (LPVOID *)&file_version_pointer, &data_size)) + return version(0, 0, 0, 0); + version_string = render_ptr(file_version_pointer); + // clean any spaces out of the string; people sometimes format these + // very badly. + for (int i = 0; i < version_string.length(); i++) { + if (version_string[i] == ' ') { + version_string.zap(i, i); + i--; // skip back a beat. + } + } +#else + return version(0, 0, 0, 0); +//tmp. +#endif + return version::from_text(version_string); +} + +bool version_checker::get_record(const astring &filename, + version_record &to_fill) +{ + to_fill = version_record(); + byte_array version_info_found(0, NIL); + if (!retrieve_version_info(filename, version_info_found)) + return false; + + basis::un_short high, low; // holds the language of the version data. + if (!get_language(version_info_found, high, low)) + return false; + + // set the root key for all accesses of the version chunk. + astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low); + + // reports whether all lookups succeeded or not. + bool total_success = true; + + // the various version pieces are retrieved... + +#ifdef __WIN32__ + basis::un_int data_size; + void *data_pointer; + + // file version. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\FileVersion")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.file_version = version::from_text(render_ptr(data_pointer)); + + // company name. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\CompanyName")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.company_name = render_ptr(data_pointer); + + // file description. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\FileDescription")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.description = render_ptr(data_pointer); + + // internal name. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\InternalName")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.internal_name = render_ptr(data_pointer); + + // copyright info. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\LegalCopyright")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.copyright = render_ptr(data_pointer); + + // trademark info. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\LegalTrademarks")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.trademarks = render_ptr(data_pointer); + + // original file name. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\OriginalFilename")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.original_name = render_ptr(data_pointer); + + // product name. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\ProductName")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.product_name = render_ptr(data_pointer); + + // product version. + if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key + + astring("\\ProductVersion")), &data_pointer, &data_size)) + total_success = false; + else + to_fill.product_version = version::from_text(render_ptr(data_pointer)); +#else + // hmmm: chunks missing in version check. +#endif + + return total_success; +} + +void version_checker::complain_wrong_version(const astring &library_file_name, + const version &expected_version, const version &version_found) const +{ + astring to_show("There has been a Version Mismatch: The module \""); + // use embedded module handle to retrieve name of dll or exe. + astring module_name = get_name(version::__global_module_handle()); + if (!module_name) module_name = "Unknown"; + to_show += module_name; + to_show += astring("\" cannot load. This is because the file \""); + to_show += library_file_name; + to_show += astring("\" was expected to have a version of ["); + + to_show += expected_version.flex_text_form(version::DOTS); + + to_show += astring("] but it instead had a version of ["); + to_show += version_found.flex_text_form(version::DOTS); + + to_show += astring("]. "); + to_show += *_version_complaint; +#ifdef __UNIX__ + continuable_error("version checking", "failure", to_show.s()); +#elif defined(__WIN32__) + MessageBox(0, to_unicode_temp(to_show), + to_unicode_temp("version_checking::failure"), MB_OK); +#endif +} + +void version_checker::complain_cannot_load(const astring &lib_file) const +{ + astring to_show("There has been a failure in Version Checking: The file \""); + to_show += lib_file; + to_show += astring("\" could not be loaded or found. "); + to_show += *_version_complaint; + continuable_error("version checking", "loading dll", to_show.s()); +} + +} //namespace. + + diff --git a/nucleus/library/versions/version_checker.h b/nucleus/library/versions/version_checker.h new file mode 100644 index 00000000..9e810155 --- /dev/null +++ b/nucleus/library/versions/version_checker.h @@ -0,0 +1,117 @@ +#ifndef VERSION_CHECKER_CLASS +#define VERSION_CHECKER_CLASS + +/*****************************************************************************\ +* * +* Name : check_version * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1996-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +namespace versions { + +//! Provides version checking for shared libraries. + +class version_checker : public virtual basis::root_object +{ +public: + version_checker(const basis::astring &library_file_name, const structures::version &expected, + const basis::astring &version_complaint); + //!< Constructs a checking object and ensures the version is appropriate. + /*!< the version checking (a call to the good_version() function) will + succeed if the library with the "library_file_name" (such as + "basis32.dll") has the "expected" version. the simplest way to check + if the version is correct is probably similar to: @code + if (!version_checker("my.dll", version(1.2.3.4)).good_version()) { + ...program exit or version failure management... + } @endcode + the "version_complaint" is the message that will be displayed on a + failure in version checking (with noisy mode enabled). it should + describe that a version failure occurred and include contact information + for the customer to get the most recent versions. for example: @code + astring version_grievance = "Please contact Floobert Corporation for " + "the latest DLL and Executable files (http://www.floobert.com)."; + @endcode */ + + virtual ~version_checker(); //!< Destructor releases any resources. + + bool good_version() const; + //!< Performs the actual version check. + /*!< If the version check is unsuccessful, then a message that + describes the problem is shown to the user and false is returned. + NOTE: the version check will also fail if the version information + structure cannot be found for that library. */ + + static basis::astring module_name(const void *module_handle); + //!< returns the module name where this object resides; only sensible on win32. + + // base requirements. + DEFINE_CLASS_NAME("version_checker"); + basis::astring text_form() const; + + static bool loaded(const basis::astring &library_file_name); + //!< returns true if the "library_file_name" is currently loaded. + static void *get_handle(const basis::astring &library_file_name); + //!< retrieves the module handle for the "library_file_name". + /*!< This returns zero if the library cannot be located. the returned + pointer wraps a win32 HMODULE currently, or it is meaningless. */ + static basis::astring get_name(const void *to_find); + //!< returns the name of the HMODULE specified by "to_find". + /*!< If that handle cannot be located, then an empty string is returned. */ + + static structures::version retrieve_version(const basis::astring &pathname); + //!< Returns the version given a "pathname" to the DLL or EXE file. + /*!< If the directory component is not included, then the search path + will be used. */ + + static bool get_record(const basis::astring &pathname, structures::version_record &to_fill); + //!< Retrieves a version record for the file at "pathname". + /*!< Returns the full version record found for a given "pathname" to + the DLL or EXE file in the record "to_fill". if the directory component + of the path is not included, then the search path will be used. false is + returned if some piece of information could not be located; this does not + necessarily indicate a total failure of the retrieval. */ + + static bool retrieve_version_info(const basis::astring &filename, + basis::byte_array &to_fill); + //!< Retrieves the version info for the "filename" into the array "to_fill". + + static bool get_language(basis::byte_array &version_chunk, basis::un_short &high, + basis::un_short &low); + //!< Gets the language identifier out of the "version_chunk". + /*!< Returns true if the language identifier for the dll's version chunk + could be stored in "high" and "low". This is a win32-only method. */ + + void complain_wrong_version(const basis::astring &library_file_name, + const structures::version &expected_version, + const structures::version &version_found) const; + //!< Reports that the file has the wrong version. + + void complain_cannot_load(const basis::astring &library_file_name) const; + //!< Reports that the dll could not be loaded. + +private: + basis::astring *_library_file_name; + structures::version *_expected_version; + basis::astring *_version_complaint; + + // forbidden. + version_checker(const version_checker &); + version_checker &operator =(const version_checker &); +}; + +} //namespace. + +#endif + diff --git a/nucleus/library/versions/version_ini.cpp b/nucleus/library/versions/version_ini.cpp new file mode 100644 index 00000000..7edb6f1e --- /dev/null +++ b/nucleus/library/versions/version_ini.cpp @@ -0,0 +1,599 @@ +/*****************************************************************************\ +* * +* Name : version_ini editing support * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "version_ini.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __WIN32__ + #include +#endif + +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; + +namespace versions { + +// the following are all strings that are sought in the version.ini file. +const char *version_ini::VERSION_SECTION = "version"; + // the section that version entries are stored under in the INI file. +const char *version_ini::COMPANY_KEY="company"; +const char *version_ini::COPYRIGHT_KEY="copyright"; +const char *version_ini::LEGAL_INFO_KEY="legal_info"; +const char *version_ini::PRODUCT_KEY="product_name"; +const char *version_ini::WEB_SITE_KEY="web_site"; + +// not used anymore; now matched with the file version's first two digits. +//const version PRODUCT_VERSION(2, 0, 0, 0); + // the current version of the entire product. + +// these are field names in the INI file. +const char *version_ini::MAJOR = "major"; +const char *version_ini::MINOR = "minor"; +const char *version_ini::REVISION = "revision"; +const char *version_ini::BUILD = "build"; +const char *version_ini::DESCRIPTION = "description"; +const char *version_ini::ROOT = "root"; +const char *version_ini::NAME = "name"; +const char *version_ini::EXTENSION = "extension"; +const char *version_ini::OLE_AUTO = "ole_auto"; + +// this is the default version INI file name, if no other is specified. +const char *VERSION_INI_FILE = "/version.ini"; + +#undef LOG +#define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t) + +version_ini::version_ini(const astring &path_name) +: _loaded(false), + _path_name(new filename(path_name)), + _ini(new ini_configurator("", ini_configurator::RETURN_ONLY)), + _held_record(new version_record) +{ + check_name(*_path_name); + _ini->name(*_path_name); +} + +version_ini::~version_ini() +{ + WHACK(_ini); + WHACK(_path_name); + WHACK(_held_record); +} + +bool version_ini::ole_auto_registering() +{ + astring extension = _ini->load(VERSION_SECTION, OLE_AUTO, ""); + return (extension.lower().t()); +} + +bool version_ini::executable() +{ + astring extension = _ini->load(VERSION_SECTION, EXTENSION, ""); + if (extension.lower() == astring("exe")) return true; + return false; +} + +bool version_ini::library() { return !executable(); } + +bool version_ini::writable() { return _path_name->is_writable(); } + +void version_ini::check_name(filename &to_examine) +{ + // if it's just a directory name, add the file name. + if (to_examine.is_directory()) { + to_examine = astring(to_examine) + VERSION_INI_FILE; + to_examine.canonicalize(); + } + + // add the directory explicitly (if it's not there already) or the ini + // writer will get it from the windows directory. + if ( (to_examine.raw()[0] != '.') && (to_examine.dirname().raw().equal_to(".")) ) { + to_examine = astring("./") + to_examine; + to_examine.canonicalize(); + } +} + +bool version_ini::executable(const astring &path_name_in) +{ + filename path_name(path_name_in); + check_name(path_name); + ini_configurator temp_ini(path_name, ini_configurator::RETURN_ONLY); + astring extension = temp_ini.load(VERSION_SECTION, EXTENSION, ""); + extension.to_lower(); + if (extension == astring("exe")) return true; + return false; +} + +bool version_ini::library(const astring &path_name) +{ return !executable(path_name); } + +version version_ini::get_version() +{ + if (_loaded) return _held_record->file_version; + get_record(); + return _held_record->file_version; +} + +void version_ini::set_version(const version &to_write, bool write_ini) +{ + _held_record->file_version = to_write; // copy the version we're given. + + // set the product version appropriately to the file version. + _held_record->product_version = to_write; + _held_record->product_version.set_component(version::REVISION, "0"); + _held_record->product_version.set_component(version::BUILD, "0"); + + if (!write_ini) return; // they don't want a change to the file. + _ini->store(VERSION_SECTION, MAJOR, to_write.get_component(version::MAJOR)); + _ini->store(VERSION_SECTION, MINOR, to_write.get_component(version::MINOR)); + _ini->store(VERSION_SECTION, REVISION, to_write.get_component(version::REVISION)); + _ini->store(VERSION_SECTION, BUILD, to_write.get_component(version::BUILD)); +} + +version version_ini::read_version_from_ini() +{ + string_array parts; + parts += _ini->load(VERSION_SECTION, MAJOR, "0"); + parts += _ini->load(VERSION_SECTION, MINOR, "0"); + parts += _ini->load(VERSION_SECTION, REVISION, "0"); + parts += _ini->load(VERSION_SECTION, BUILD, "0"); + return version(parts); +} + +version_record &version_ini::access_record() { return *_held_record; } + +version_record version_ini::get_record() +{ +// FUNCDEF("get_record"); + if (_loaded) return *_held_record; + version_record to_return; + to_return.description = _ini->load(VERSION_SECTION, DESCRIPTION, ""); + to_return.file_version = read_version_from_ini(); + to_return.internal_name = _ini->load(VERSION_SECTION, NAME, ""); + to_return.original_name = _ini->load(VERSION_SECTION, ROOT, ""); + to_return.original_name += "."; + + // the dll type of extension is a hard default. anything besides the exe + // ending gets mapped to dll. + astring extension = _ini->load(VERSION_SECTION, EXTENSION, ""); + extension.to_lower(); + if (extension.equal_to("dll")) {} + else if (extension.equal_to("exe")) {} + else extension.equal_to("dll"); + to_return.original_name += extension; + + to_return.product_version = to_return.file_version; + to_return.product_version.set_component(version::REVISION, "0"); + to_return.product_version.set_component(version::BUILD, "0"); + + to_return.product_name = _ini->load(VERSION_SECTION, PRODUCT_KEY, ""); + to_return.company_name = _ini->load(VERSION_SECTION, COMPANY_KEY, ""); + to_return.copyright = _ini->load(VERSION_SECTION, COPYRIGHT_KEY, ""); + to_return.trademarks = _ini->load(VERSION_SECTION, LEGAL_INFO_KEY, ""); + to_return.web_address = _ini->load(VERSION_SECTION, WEB_SITE_KEY, ""); + + *_held_record = to_return; + _loaded = true; + return to_return; +} + +void version_ini::set_record(const version_record &to_write, bool write_ini) +{ + *_held_record = to_write; + if (write_ini) { + _ini->store(VERSION_SECTION, DESCRIPTION, to_write.description); + set_version(to_write.file_version, write_ini); + _ini->store(VERSION_SECTION, ROOT, to_write.original_name); + _ini->store(VERSION_SECTION, NAME, to_write.internal_name); + } + _loaded = true; // we consider this to be the real version now. +} + +////////////// + +const astring version_rc_template = "\ +#ifndef NO_VERSION\n\ +#include \n\ +#include <__build_version.h>\n\ +#include <__build_configuration.h>\n\ +#define BI_PLAT_WIN32\n\ + // force 32 bit compile.\n\ +1 VERSIONINFO LOADONCALL MOVEABLE\n\ +FILEVERSION __build_FILE_VERSION_COMMAS\n\ +PRODUCTVERSION __build_PRODUCT_VERSION_COMMAS\n\ +FILEFLAGSMASK 0\n\ +FILEFLAGS VS_FFI_FILEFLAGSMASK\n\ +#if defined(BI_PLAT_WIN32)\n\ + FILEOS VOS__WINDOWS32\n\ +#else\n\ + FILEOS VOS__WINDOWS16\n\ +#endif\n\ +FILETYPE VFT_APP\n\ +BEGIN\n\ + BLOCK \"StringFileInfo\"\n\ + BEGIN\n\ + // Language type = U.S. English(0x0409) and Character Set = Windows, Multilingual(0x04b0)\n\ + BLOCK \"040904b0\" // Matches VarFileInfo Translation hex value.\n\ + BEGIN\n\ + VALUE \"CompanyName\", __build_company \"\\000\"\n\ +#ifndef _DEBUG\n\ + VALUE \"FileDescription\", \"$file_desc\\000\"\n\ +#else\n\ + VALUE \"FileDescription\", \"$file_desc (DEBUG)\\000\"\n\ +#endif\n\ + VALUE \"FileVersion\", __build_FILE_VERSION \"\\000\" \n\ + VALUE \"ProductVersion\", __build_PRODUCT_VERSION \"\\000\" \n\ + VALUE \"InternalName\", \"$internal\\000\"\n\ + VALUE \"LegalCopyright\", __build_copyright \"\\000\"\n\ + VALUE \"LegalTrademarks\", __build_legal_info \"\\000\"\n\ + VALUE \"OriginalFilename\", \"$original_name\\000\"\n\ + VALUE \"ProductName\", __build_product_name \"\\000\"\n\ + $special_ole_flag\n\ + END\n\ + END\n\ +\n\ + BLOCK \"VarFileInfo\"\n\ + BEGIN\n\ + VALUE \"Translation\", 0x0409, 0x04b0 // US English (0x0409) and win32 multilingual (0x04b0)\n\ + END\n\ +END\n\ +#endif\n"; + +////////////// + +// replaces every occurrence of the keyword in "tag" with the "replacement". +#define REPLACE(tag, replacement) \ + new_version_entry.replace_all(tag, replacement); \ + +bool version_ini::write_rc(const version_record &to_write) +{ + astring new_version_entry(version_rc_template); + + // $file_ver -> w, x, y, z for version of the file. + REPLACE("$file_ver", to_write.file_version.flex_text_form(version::COMMAS)); + + // $prod_ver -> w, x, y, z for version of the product. + REPLACE("$prod_ver", to_write.product_version.flex_text_form + (version::COMMAS)); + + // $company -> name of company. + REPLACE("$company", to_write.company_name); + + // $file_desc -> description of file. + astring description_release = to_write.description; + REPLACE("$file_desc", description_release); + astring description_debug = to_write.description + + astring(" -- Debug Version"); + REPLACE("$file_desc", description_debug); + + // $file_txt_ver -> file version in form w.x.y.z. + REPLACE("$file_txt_ver", to_write.file_version.flex_text_form(version::DOTS)); + + // $internal -> internal name of the library, without extensions? + REPLACE("$internal", to_write.internal_name); + + // $copyright -> copyright held by us. + REPLACE("$copyright", to_write.copyright); + + // $legal_tm -> the legal trademarks that must be included, e.g. windows? + REPLACE("$legal_tm", to_write.trademarks); + + // $original_name -> the file's name before possible renamings. + REPLACE("$original_name", to_write.original_name); + + // $prod_name -> the name of the product that this belongs to. + REPLACE("$prod_name", to_write.product_name); + + // $prod_txt_ver -> product version in form w.x.y.z. + REPLACE("$prod_txt_ver", to_write.product_version + .flex_text_form(version::DOTS, version::MINOR)); + + astring special_filler; // nothing by default. + if (ole_auto_registering()) + special_filler = "VALUE \"OLESelfRegister\", \"\\0\""; + REPLACE("$special_ole_flag", special_filler); + + astring root_part = "/"; + root_part += _ini->load(VERSION_SECTION, ROOT, ""); + + astring rc_filename(astring(_path_name->dirname()) + root_part + + "_version.rc"); + + filename(rc_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); + // make sure we can write to the file. + + byte_filer rc_file(rc_filename, "w"); + if (!rc_file.good()) return false; + rc_file.write((abyte *)new_version_entry.s(), new_version_entry.length()); + rc_file.close(); + return true; +} + +////////////// + +const astring version_header_template = "\ +#ifndef $lib_prefix_VERSION_HEADER\n\ +#define $lib_prefix_VERSION_HEADER\n\ +\n\ +/*****************************************************************************\\\n\ +* *\n\ +* Name : Version header for $lib_name\n\ +* Author : Automatically generated by version_stamper *\n\ +* *\n\ +\\*****************************************************************************/\n\ +\n\ +#include <__build_version.h>\n\ +#include <__build_configuration.h>\n\ +#include \n\ +#include \n\ +\n\ +#ifdef __WIN32__\n\ +\n\ +// this macro can be used to check that the current version of the\n\ +// $lib_name library is the same version as expected. to use it, check\n\ +// whether it returns true or false. if false, the version is incorrect.\n\ +#define CHECK_$lib_prefix() (version_checker(astring(\"$lib_prefix\")\\\n\ + + astring(\".dll\"), version(__build_SYSTEM_VERSION),\\\n\ + astring(\"Please contact $company_name for the latest DLL and \"\\\n\ + \"Executable files ($web_address).\")).good_version())\n\ +\n\ +#else\n\ +\n\ +// null checking for embedded or other platforms without versions.\n\ +\n\ +#define CHECK_$lib_prefix() 1\n\ +\n\ +#endif //__WIN32__\n\ +\n\ +#endif\n\ +\n"; + +////////////// + +bool version_ini::write_code(const version_record &to_write) +{ + astring root_part = _ini->load(VERSION_SECTION, ROOT, ""); + astring root = root_part.upper(); // make upper case for naming sake. + astring name = _ini->load(VERSION_SECTION, NAME, ""); + // replace the macros here also. + name.replace_all("$product_name", to_write.product_name); + + astring new_version_entry(version_header_template); + +//some of the replacements are no longer needed. + + // $lib_prefix -> the first part of the library's name. + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + REPLACE("$lib_prefix", root); + + // $lib_name -> the name of the library, as it thinks of itself. + REPLACE("$lib_name", name); + REPLACE("$lib_name", name); + REPLACE("$lib_name", name); + + // $lib_version -> the current version for this library. + REPLACE("$lib_version", to_write.file_version.flex_text_form(version::COMMAS)); + + // $company_name -> the company that produces the library. + REPLACE("$company_name", to_write.company_name); + + // $web_address -> the web site for the company. not actually stored. + REPLACE("$web_address", to_write.web_address); + + astring header_filename(_path_name->dirname().raw() + "/" + root_part + + astring("_version.h")); + + filename(header_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); + // make sure we can write to the file. + + byte_filer header(header_filename, "w"); + if (!header.good()) return false; + header.write((abyte *)new_version_entry.s(), new_version_entry.length()); + header.close(); + return true; +} + + +// returns true if manipulated the full_string to replace its version +bool replace_version_entry(astring &full_string, const astring &look_for, + const astring &new_ver) +{ + bool to_return = false; + int posn = 0; // where are we looking for this? + + while (posn < full_string.length()) { + int ver_posn = full_string.find(look_for, posn); + if (ver_posn < 0) break; // nothing to modify. + int quote_posn = full_string.find("\"", ver_posn); + if (quote_posn < 0) break; // malformed assembly we will not touch. + int second_quote_posn = full_string.find("\"", quote_posn + 1); + if (second_quote_posn < 0) break; // more malformage. + full_string.zap(quote_posn + 1, second_quote_posn - 1); + full_string.insert(quote_posn + 1, new_ver); + to_return = true; // found a match. + // skip to the next place. + posn = quote_posn + new_ver.length(); + } + + return to_return; +} + +bool version_ini::write_assembly(const version_record &to_write, + bool do_logging) +{ +// FUNCDEF("write_assembly"); + filename just_dir = _path_name->dirname(); +//LOG(astring("dir is set to: ") + just_dir); + directory dir(just_dir); + filename to_patch; +//LOG(astring("dir has: ") + dir.files().text_form()); + if (non_negative(dir.files().find("AssemblyInfo.cpp"))) + to_patch = just_dir.raw() + "/AssemblyInfo.cpp"; + else if (non_negative(dir.files().find("AssemblyInfo.cs"))) + to_patch = just_dir.raw() + "/AssemblyInfo.cs"; + if (!to_patch.raw()) { + // no assembly file yet. see if there's one in a properties subdirectory. + directory dir2(just_dir + "/Properties"); + if (non_negative(dir2.files().find("AssemblyInfo.cpp"))) + to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cpp"; + else if (non_negative(dir2.files().find("AssemblyInfo.cs"))) + to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cs"; + } + + if (to_patch.raw().t()) { + // we have a filename to work on. + filename(to_patch).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS); + // make sure we can write to the file. + byte_filer modfile(to_patch, "r+b"); + astring contents; + modfile.read(contents, 1000000); // read any file size up to that. + while (contents[contents.end()] == '\032') { + // erase any stray eof characters that may happen to be present. + contents.zap(contents.end(), contents.end()); + } +//LOG(astring("file contents are: \n") + contents); + +//here's where to fixit. + + astring ver_string = to_write.file_version.flex_text_form(version::DOTS); + bool did_replace = replace_version_entry(contents, "AssemblyVersionAttribute", ver_string); + if (!did_replace) { + did_replace = replace_version_entry(contents, "AssemblyVersion", ver_string); + } + if (!did_replace) return true; // nothing to modify? + did_replace = replace_version_entry(contents, "AssemblyFileVersion", ver_string); + if (!did_replace) { + did_replace = replace_version_entry(contents, "AssemblyFileVersionAttribute", ver_string); + } + // if we got to here, we at least replaced something... + +/* + int ver_posn = contents.find("AssemblyVersionAttribute", 0); + // try again if that seek failed. + if (ver_posn < 0) + ver_posn = contents.find("AssemblyVersion", 0); + if (ver_posn < 0) return true; // nothing to modify. +//LOG(astring("found assembly version: ") + to_patch); + int quote_posn = contents.find("\"", ver_posn); + if (quote_posn < 0) return true; // malformed assembly we will not touch. +//LOG(astring("found quote: ") + to_patch); + int second_quote_posn = contents.find("\"", quote_posn + 1); + if (second_quote_posn < 0) return true; // more malformage. +//LOG(astring("found quote: ") + to_patch); + contents.zap(quote_posn + 1, second_quote_posn - 1); + contents.insert(quote_posn + 1, ver_string); +*/ + +//LOG(astring("writing new output file: ") + to_patch); + modfile.seek(0); + modfile.write(contents); + modfile.truncate(); // chop off anything left from previous versions. + if (do_logging) { + // let the people know about this... + filename dirbase = filename(modfile.filename()).dirname().basename(); + filename just_base = filename(modfile.filename()).basename(); + program_wide_logger::get().log(astring(" patching: ") + dirbase + + "/" + just_base, basis::ALWAYS_PRINT); + } + } + + return true; +} + +bool version_ini::one_stop_version_stamp(const astring &path, + const astring &source_version, bool do_logging) +{ + astring path_name = path; + if (path_name.equal_to(".")) + path_name = application_configuration::current_directory(); + + // load the version record in from the ini file and cache it. + version_ini source(path_name); + source.get_record(); + + if (source_version.t()) { + // get the version structure from the passed file. + version_ini main_version(source_version); + version version_to_use = main_version.get_version(); + + // stuff the version from the main source into this particular file. + source.set_version(version_to_use, false); + + // stuff the other volatile records from the main version. + version_record main = main_version.get_record(); + source.access_record().company_name = main.company_name; + source.access_record().web_address = main.web_address; + source.access_record().copyright = main.copyright; + source.access_record().trademarks = main.trademarks; + source.access_record().product_name = main.product_name; + + source.access_record().internal_name.replace("$product_name", + source.get_record().product_name); + } + + if (do_logging) { + // report the current state. + program_wide_logger::get().log(source.get_record().internal_name + " version " + + source.get_version().text_form() + ".", ALWAYS_PRINT); + } + + version_ini verini(path_name); + verini.set_record(source.get_record(), false); + +// LOG(a_sprintf("The file \"%s\" contains this version information:", +// path_name.s())); +// LOG(verini.get_record().text_form()); + + if (!verini.write_rc(verini.get_record())) { + critical_events::alert_message(a_sprintf("Could not write the RC file in \"%s\".", + filename(path_name).basename().raw().s())); + return false; + } + + if (verini.library() && !verini.write_code(verini.get_record())) { + critical_events::alert_message(astring("Could not write the C++ header file for " + "the directory \"") + + filename(path_name).basename() + astring("\".\n")); + return false; + } + + if (!verini.write_assembly(verini.get_record(), do_logging)) { + critical_events::alert_message(astring("Could not write the Assembly info file for " + "the directory \"") + + filename(path_name).basename() + astring("\".\n")); + return false; + } + + return true; +} + +} //namespace. diff --git a/nucleus/library/versions/version_ini.h b/nucleus/library/versions/version_ini.h new file mode 100644 index 00000000..6838adee --- /dev/null +++ b/nucleus/library/versions/version_ini.h @@ -0,0 +1,143 @@ +#ifndef VERSION_INI_CLASS +#define VERSION_INI_CLASS + +/*****************************************************************************\ +* * +* Name : version_ini editing support * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +namespace versions { + +//! This provides support for writing windows version files. +/*! + The class loads version information out of an initialization file and writes + it into a standard windows VERSION_INFO RC file entry. It also creates the + headers we use with our version control support. +*/ + +class version_ini : public virtual basis::root_object +{ +public: + version_ini(const basis::astring &path_name); + //!< the "path_name" is where the version INI file is located. + /*!< this file will be read for the key fields that will be used to name + the version RC file and generate information in the resource. */ + + ~version_ini(); + + DEFINE_CLASS_NAME("version_ini"); + + bool writable(); + //!< returns true if the INI file specified in the constructor is writable. + + structures::version get_version(); + //!< observes or modifies the version number structure held here. + /*!< if the record had not been previously loaded, it is loaded. */ + + void set_version(const structures::version &to_write, bool write_to_ini); + //!< sets the version held here. + /*!< if "write_ini" is true, then the ini file is written to also. note + that if get_record() had not previously been done and "write_ini" is not + true, then the next get_record() or get_version() will wipe out the + version that is set in the interim. */ + + structures::version_record get_record(); + //!< observes the entire version record. + /*!< The information is assembled from any cached record, the ini file and + other sources. if the record has been loaded before, the last loaded + version is returned plus any changes that have been made to the held + record since then. otherwise, the record is read from the ini file. */ + + structures::version_record &access_record(); + //!< provides access to change the version_record held here. + /*!< this does not read from the INI file, unlike get_record(). */ + + void set_record(const structures::version_record &to_write, bool write_to_ini); + //!< modifies the entire version record. + /*!< if "write_to_ini" is true, then the new information is actually + written to our configuration file. otherwise the information just + replaces the source record here. */ + + bool executable(); + //!< returns true if this version file is for an executable. + bool library(); + //!< returns true if this version file is for a dynamic library. + + bool ole_auto_registering(); + //!< returns true if this version file specifies ole auto registration. + + bool write_rc(const structures::version_record &to_write); + //!< writes out the file 'X_version.rc' for the X library or application. + /*!< the contents will include the information specified in "to_write", + where X is the library name from that record. */ + + bool write_code(const structures::version_record &to_write); + //!< writes out the header ('X_version.h') with the version information. + /*!< this file is needed for other libraries or application to know this + project's version number. the users can make sure that the header info + agrees with the actual version seen on the file. */ + + bool write_assembly(const structures::version_record &to_write, bool do_logging); + //!< fixes any assemblies with the info in "to_write". + + static bool executable(const basis::astring &path_name); + //!< returns true if "path_name" is for an executable. + static bool library(const basis::astring &path_name); + //!< returns true if "path_name" is for a dynamic library. + + structures::version read_version_from_ini(); + //!< specialized version ignores cache and gets version directly from file. + + static bool one_stop_version_stamp(const basis::astring &path, + const basis::astring &source_version, bool do_logging); + //!< performs version stamping using the ini file in "path". + /*!< "source_version" supplies the name of the main version file where + we retrieve the current version numbers. if that is not specified, then + only the version header and RC file are created. if "do_logging" is + true, then version info will be sent to the program-wide logger. */ + + // constants for strings found in the version INI file. + static const char *VERSION_SECTION; + static const char *COMPANY_KEY; + static const char *COPYRIGHT_KEY; + static const char *LEGAL_INFO_KEY; + static const char *PRODUCT_KEY; + static const char *WEB_SITE_KEY; + static const char *MAJOR; + static const char *MINOR; + static const char *REVISION; + static const char *BUILD; + static const char *DESCRIPTION; + static const char *ROOT; + static const char *NAME; + static const char *EXTENSION; + static const char *OLE_AUTO; + +private: + bool _loaded; //!< did we grab the data from the ini file yet? + filesystem::filename *_path_name; //!< where to find the ini file. + configuration::ini_configurator *_ini; //!< accesses the ini file. + structures::version_record *_held_record; //!< the data we cache for the ini file. + + static void check_name(filesystem::filename &to_examine); + //!< adds the default file name if "to_examine" turns out to be a directory. +}; + +} //namespace. + +#endif + diff --git a/nucleus/makefile b/nucleus/makefile new file mode 100644 index 00000000..2334be05 --- /dev/null +++ b/nucleus/makefile @@ -0,0 +1,7 @@ +include variables.def + +PROJECT = core_modules +BUILD_BEFORE = library applications tools + +include rules.def + diff --git a/nucleus/tools/clam_tools/makefile b/nucleus/tools/clam_tools/makefile new file mode 100644 index 00000000..b6869892 --- /dev/null +++ b/nucleus/tools/clam_tools/makefile @@ -0,0 +1,19 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +PROJECT = clam_tools +TYPE = application +DEFINITIONS += __BUILD_STATIC_APPLICATION__ + +# why was that there? +#LIBS_USED += pthread +# + +ifeq "$(OMIT_VERSIONS)" "" + SOURCE += clamtools_version.rc +endif +TARGETS = value_tagger.exe version_stamper.exe vsts_version_fixer.exe write_build_config.exe + +include cpp/rules.def + diff --git a/nucleus/tools/clam_tools/value_tagger.cpp b/nucleus/tools/clam_tools/value_tagger.cpp new file mode 100644 index 00000000..df1724a4 --- /dev/null +++ b/nucleus/tools/clam_tools/value_tagger.cpp @@ -0,0 +1,1003 @@ +/*****************************************************************************\ +* * +* Name : value_tagger * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Scoots through the entire known code base and builds a list of all the * +* outcome (and filter) values for that tree. A manifest of the names is * +* produced. Most of the behavior is driven by the ini file whose name is * +* passed on the command line. * +* Note that the set of items that can be searched for can be specified * +* in the ini file, although they must follow the format of: * +* pattern(name, value, description) * +* where the "pattern" is the search term and the other three items specify * +* the enumerated value to be marked. * +* * +******************************************************************************* +* Copyright (c) 2005-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __WIN32__ + #include +#endif + +#undef LOG +#define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), astring(s)) + +using namespace algorithms; +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace timely; + +const int LONGEST_SEPARATION = 128; + // the longest we expect a single line of text to be in definition blocks. + // if the definition of an outcome or whatever is farther away than this + // many characters from a comment start, we will no longer consider the + // line to be commented out. this pretty much will never happen unless it's + // intentionally done to break this case. + +const char *SKIP_VALUE_PHRASE = "SKIP_TO_VALUE"; + // the special phrase we use to indicate that values should jump to + // a specific number. + +//////////////////////////////////////////////////////////////////////////// + +// this object records all the data that we gather for the defined items. +class item_record +{ +public: + astring _name; + int _value; + astring _description; + astring _path; + astring _extra_tag; //!< records special info for links. + + item_record(const astring &name = astring::empty_string(), int value = 999, + const astring &description = astring::empty_string(), + const astring &path = astring::empty_string(), + const astring &extra_tag = astring::empty_string()) + : _name(name), _value(value), _description(description), _path(path), + _extra_tag(extra_tag) {} +}; + +//////////////////////////////////////////////////////////////////////////// + +class search_record +{ +public: + search_record(const astring &search = astring::empty_string(), + bool is_link = false, search_record *link = NIL) + : _search(search), _no_modify(false), _is_link(is_link), _our_link(link), + _current_value(0), _value_increment(1) {} + + // these properties are available for both real or linked records. + astring _search; // our term to search for in the files. + bool _no_modify; // true if values should not be automatically incremented. + astring _tag; // extra information attached to this type. + + bool is_link() const { return _is_link; } + // returns true if this object is leeching off another object for data. + + search_record *our_link() const { return _our_link; } + // returns the object that this object is a mere shadow of. + + symbol_table &definitions() { + if (is_link()) return _our_link->_definitions; + else return _definitions; + } + + int ¤t_value() { + if (is_link()) return _our_link->_current_value; + else return _current_value; + } + + int &value_increment() { + if (is_link()) return _our_link->_value_increment; + else return _value_increment; + } + + int_set &out_of_band() { + if (is_link()) return _our_link->_out_of_band; + else return _out_of_band; + } + +private: + bool _is_link; // true if this object links to another. + search_record *_our_link; // the search we share for our values. + symbol_table _definitions; + // the definitions that we found in the code. + int _current_value; // the next value to use for our term. + int _value_increment; + // how much to add for each new value, if this is an incrementing search. + int_set _out_of_band; + // values we've seen that were premature. we always want to honor this + // set, if it exists, but there will be nothing in it if the search has + // completely standard non-incrementing type. this could be varied by + // a non-incrementer linking to a standard incrementer. +}; + +//! a table of terms that we will search for in the code. +class active_searches : public symbol_table +{}; + +//////////////////////////////////////////////////////////////////////////// + +// this class provides us a way to easily sort our items based on value. + +class simple_sorter { +public: + int _index; + int _value; + simple_sorter(int index = 0, int value = 0) : _index(index), _value(value) {} + bool operator < (const simple_sorter &to_compare) const + { return _value < to_compare._value; } + bool operator == (const simple_sorter &to_compare) const + { return _value == to_compare._value; } +}; + +class sorting_array : public array {}; + +//////////////////////////////////////////////////////////////////////////// + +class value_tagger : public application_shell +{ +public: + value_tagger(); + virtual ~value_tagger(); + DEFINE_CLASS_NAME("value_tagger"); + int execute(); + int print_instructions_and_exit(); + + bool process_tree(const astring &path); + // called on each directory hierarchy that we need to process. + + bool process_file(const astring &path); + // examines the file specified to see if it matches our needs. + + bool parse_define(const astring &scanning, int indy, astring &name, + int &value, astring &description, int &num_start, int &num_end); + // processes the string in "scanning" to find parentheses surrounding + // the "name", "value" and "description". the "description" field may + // occupy multiple lines, so all are gathered together to form one + // unbroken string. the "num_start" and "num_end" specify where the + // numeric value was found, in case it needs to be patched. + +private: + ini_configurator *_ini; // the configuration for what we'll scan. + string_table _dirs; // the list of directories. + string_table _dirs_seen; // full list of already processed directories. + astring _manifest_filename; // the name of the manifest we'll create. + byte_filer _manifest; // the actual file we're building. + active_searches _search_list; // tracks our progress in scanning files. + int_array _search_ordering; + // lists the terms in the order they should be applied. initially this + // carries the first pass items, but later will be reset for second pass. + int_array _postponed_searches; + // lists the searches that must wait until the main search is done. + string_table _modified_files; // the list of files that we touched. +}; + +//////////////////////////////////////////////////////////////////////////// + +value_tagger::value_tagger() +: application_shell(), + _ini(NIL), + _dirs_seen(10) +{ +} + +value_tagger::~value_tagger() +{ + WHACK(_ini); +} + +int value_tagger::print_instructions_and_exit() +{ + LOG(a_sprintf("%s usage:", filename(_global_argv[0]).basename().raw().s())); + LOG(""); + LOG("\ +This utility scans a code base for outcome and filter definitions. It will\n\ +only scan the header files (*.h) found in the directories specified. The\n\ +single parameter is expected to be an INI filename that contains the scanning\n\ +configuration. The INI file should be formatted like this (where the $HOME\n\ +can be any variable substitution from the environment):"); + LOG(""); + LOG("\ +[manifest]\n\ +output=$HOME/manifest.txt\n\ +\n\ +[searches]\n\ +DEFINE_OUTCOME=1\n\ +DEFINE_FILTER=1\n\ +\n\ +[directories]\n\ +$HOME/source/lib_src/library/basis\n\ +$HOME/source/lib_src/library\n\ +$HOME/source/lib_src/communication/sockets\n\ +$HOME/source/lib_src/communication\n\ +$HOME/source/lib_src\n\ +$HOME/source/app_src\n\ +$HOME/source/test_src\n\ +\n\ +[DEFINE_OUTCOME]\n\ +first=0\n\ +increment=-1\n\ +\n\ +[DEFINE_FILTER]\n\ +first=-1\n\ +increment=1\n\ +no_modify=1\n\ +\n\ +[DEFINE_API_OUTCOME]\n\ +no_modify=1\n\ +link=DEFINE_OUTCOME\n\ +tag=API\n\ +\n\ + The \"first\" field defines the starting value that should be assigned to\n\ +items.\n\ + The \"increment\" field specifies what to add to a value for the next item.\n\ + The optional \"no_modify\" flag means that the values should not be auto-\n\ +incremented; their current value will be used.\n\ + The optional \"link\" field defines this type of item as using the current\n\ +values for another type of item. In this case, API_OUTCOME will use the\n\ +values for OUTCOME to share its integer space, but API_OUTCOME is not auto-\n\ +incremented even though OUTCOME is. This causes the values for OUTCOME and\n\ +API_OUTCOME to be checked for uniqueness together, but only OUTCOME will be\n\ +auto-incremented. Note that only one level of linking is supported currently.\n\ + The optional \"tag\" can be used to distinguish the entries for a particular\n\ +search type if needed. This is most helpful for links, so that they can be\n\ +distinguished from their base type.\n\ +\n\ +"); + + return 23; +} + +astring header_string(const astring &build_number) +{ + return a_sprintf("\ +#ifndef GENERATED_VALUES_MANIFEST\n\ +#define GENERATED_VALUES_MANIFEST\n\ +\n\ +// This file contains all outcomes and filters for this build.\n\ +\n\ +// Generated for build %s on %s\n\ +\n\ +", build_number.s(), time_stamp::notarize(true).s()); +} + +astring footer_string(const byte_array &full_config_file) +{ + return a_sprintf("\n\ +// End of definitions.\n\ +\n\ +\n\ +// The following is the full configuration for this build:\n\ +\n\ +/*\n\ +\n\ +%s\n\ +*/\n\ +\n\ +\n\ +#endif // outer guard.\n\ +", (char *)full_config_file.observe()); +} + +int value_tagger::execute() +{ + FUNCDEF("execute"); + if (_global_argc < 2) { + return print_instructions_and_exit(); + } + + log(time_stamp::notarize(true) + "value_tagger started.", basis::ALWAYS_PRINT); + + astring test_repository = environment::get("REPOSITORY_DIR"); + if (!test_repository) { + astring msg = "\ +There is a problem with a required build precondition. The following\r\n\ +variables must be set before the build is run:\r\n\ +\r\n\ + REPOSITORY_DIR This should point at the root of the build tree.\r\n\ +\r\n\ +There are also a few variables only required for CLAM-based compilation:\r\n\ +\r\n\ + MAKEFLAGS This should be set to \"-I $REPOSITORY_DIR/clam\".\r\n\ +\r\n\ +Note that on Win32 platforms, these should be set in the System or User\r\n\ +variables before running a build.\r\n"; +#ifdef __WIN32__ + ::MessageBox(0, to_unicode_temp(msg), + to_unicode_temp("Missing Precondition"), MB_ICONWARNING|MB_OK); +#endif + non_continuable_error(class_name(), func, msg); + } + + astring ini_file = _global_argv[1]; // the name of our ini file. + _ini = new ini_configurator(ini_file, ini_configurator::RETURN_ONLY); + + // read the name of the manifest file to create. + _manifest_filename = _ini->load("manifest", "output", ""); + if (!_manifest_filename) { + non_continuable_error(class_name(), ini_file, "The 'output' file entry is missing"); + } + _manifest_filename = parser_bits::substitute_env_vars(_manifest_filename); + + LOG(astring("Sending Manifest to ") + _manifest_filename); + LOG(""); + + filename(_manifest_filename).unlink(); + // clean out the manifest ahead of time. + + // read the list of directories to scan for code. + string_table temp_dirs; + bool read_dirs = _ini->get_section("directories", temp_dirs); + if (!read_dirs || !temp_dirs.symbols()) { + non_continuable_error(class_name(), ini_file, + "The 'directories' section is missing"); + } + for (int i = 0; i < temp_dirs.symbols(); i++) { +//log(astring("curr is ") + current); + astring current = parser_bits::substitute_env_vars(temp_dirs.name(i)); + _dirs.add(current, ""); + } + + LOG(astring("Directories to scan...")); + LOG(_dirs.text_form()); + + astring rdir = environment::get("REPOSITORY_DIR"); + astring fname; + astring parmfile = environment::get("BUILD_PARAMETER_FILE"); + if (parmfile.t()) fname = parmfile; + else fname = rdir + "/build.ini"; + + // read the list of search patterns. + string_table searches; + bool read_searches = _ini->get_section("searches", searches); + if (!read_searches || !searches.symbols()) { + non_continuable_error(class_name(), ini_file, + "The 'searches' section is missing"); + } + + LOG("Searching for..."); + LOG(searches.text_form()); + + // now make sure that we get the configuration for each type of value. + for (int i = 0; i < searches.symbols(); i++) { + const astring &curr_name = searches.name(i); + + search_record *check_search = _search_list.find(curr_name); + if (check_search) { + non_continuable_error(class_name(), ini_file, + astring("section ") + curr_name + " is being defined twice"); + } + + { + // check for whether this section is linked to another or not. + astring linked = _ini->load(curr_name, "link", ""); + search_record *our_link_found = NIL; + if (linked.t()) { + // we found that this should be linked to another item. + our_link_found = _search_list.find(linked); + if (!our_link_found) { + non_continuable_error(class_name(), ini_file, + astring("linked section ") + curr_name + " is linked to missing " + "section " + linked); + } + search_record new_guy(curr_name, true, our_link_found); + _search_list.add(curr_name, new_guy); + } else { + // this section is a stand-alone section. + search_record new_guy(curr_name); + _search_list.add(curr_name, new_guy); + } + } + + // find our new search cabinet again so we can use it. + search_record *curr_search = _search_list.find(curr_name); + if (!curr_search) { + non_continuable_error(class_name(), ini_file, + astring("section ") + curr_name + " is missing from table " + "after addition; logic error"); + } + + // specify some defaults first. + int start = 0; + int increm = 1; + if (!curr_search->is_link()) { + // a linked object doesn't get to specify starting value or increment. + start = _ini->load(curr_name, "first", start); + curr_search->current_value() = start; + increm = _ini->load(curr_name, "increment", increm); + curr_search->value_increment() = increm; + } else { + start = curr_search->our_link()->current_value(); + increm = curr_search->our_link()->value_increment(); + } + + int no_modify = _ini->load(curr_name, "no_modify", 0); + if (no_modify) { + curr_search->_no_modify = true; + } + + astring tag = _ini->load(curr_name, "tag", ""); + if (tag.t()) { + curr_search->_tag = tag; + } + + a_sprintf to_show("%s: no_modify=%s", curr_name.s(), + no_modify? "true" : "false"); + + if (curr_search->is_link()) { + // links show who they're hooked to. + to_show += astring(" link=") + curr_search->our_link()->_search; + } else { + // non-links get to show off their start value and increment. + to_show += a_sprintf(" start=%d increment=%d", start, increm); + } + if (tag.t()) { + to_show += astring(" tag=") + curr_search->_tag; + } + LOG(to_show); + } + LOG(""); + + // now gather some info about the build that we can plug into the manifest. + + byte_filer build_file(fname, "r"); + if (!build_file.good()) { + non_continuable_error(class_name(), build_file.filename(), + "Could not find the build configuration; is REPOSITORY_DIR set?"); + } + byte_array full_config; + build_file.read(full_config, 100000); // a good chance to be big enough. + build_file.close(); + +//log("got config info:"); +//log((char *)full_config.observe()); + + astring build_number; + ini_configurator temp_ini(fname, configurator::RETURN_ONLY); + build_number += temp_ini.load("version", "major", ""); + build_number += "."; + build_number += temp_ini.load("version", "minor", ""); + build_number += "."; + build_number += temp_ini.load("version", "revision", ""); + build_number += "."; + build_number += temp_ini.load("version", "build", ""); + if (build_number.equal_to("...")) { + non_continuable_error(class_name(), build_file.filename(), + "Could not read the build number; is build parameter file malformed?"); + } + +//log(astring("got build num: ") + build_number); + + // now that we know what file to create, write the header blob for it. + _manifest.open(_manifest_filename, "wb"); + if (!_manifest.good()) { + non_continuable_error(class_name(), _manifest_filename, + "Could not write to the manifest file!"); + } + _manifest.write(header_string(build_number)); + + // make sure we have the right ordering for our terms. items that are + // non-modify types must come before the modifying types. + for (int i = 0; i < _search_list.symbols(); i++) { + search_record &curr_reco = _search_list[i]; + if (curr_reco._no_modify) + _search_ordering += i; + else + _postponed_searches += i; + } + + // scan across each directory specified for our first pass. + LOG("First pass..."); + for (int i = 0; i < _dirs.symbols(); i++) { + if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue; // skip comment. + LOG(astring(" Processing: ") + _dirs.name(i)); + bool ret = process_tree(_dirs.name(i)); + if (!ret) { + LOG(astring("Problem encountered in directory ") + _dirs.name(i)); + } + } + LOG(""); + + // second pass now. + LOG("Second pass..."); + _search_ordering = _postponed_searches; // recharge the list for 2nd pass. + _dirs_seen.reset(); // drop any directories we saw before. + for (int i = 0; i < _dirs.symbols(); i++) { + if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue; // skip comment. + LOG(astring(" Processing: ") + _dirs.name(i)); + bool ret = process_tree(_dirs.name(i)); + if (!ret) { + LOG(astring("Problem encountered in directory ") + _dirs.name(i)); + } + } + LOG(""); + + const astring quote = "\""; + const astring comma = ","; + + // scoot across all the completed searches and dump results. + for (int i = 0; i < _search_list.symbols(); i++) { + search_record &curr_reco = _search_list[i]; + const astring &pattern = curr_reco._search; + + _manifest.write(astring("/* START ") + pattern + "\n"); + _manifest.write(astring("[") + pattern + "]\n"); + + if (!curr_reco.is_link()) { + // scoot across all definitions and print them out. + + // do the print out in order, as dictated by the sign of the increment. + sorting_array sortie; + for (int j = 0; j < curr_reco.definitions().symbols(); j++) { + const item_record &rec = curr_reco.definitions().get(j); + sortie += simple_sorter(j, rec._value); + } + shell_sort(sortie.access(), sortie.length(), + negative(curr_reco.value_increment())); + + for (int j = 0; j < sortie.length(); j++) { + int indy = sortie[j]._index; + const item_record &rec = curr_reco.definitions().get(indy); + astring to_write = " "; + if (rec._extra_tag.t()) { + to_write += astring("(") + rec._extra_tag + ") "; + } + to_write += quote + rec._name + quote + comma + " "; + to_write += quote + a_sprintf("%d", rec._value) + quote + comma + " "; + to_write += quote + rec._description + quote + comma + " "; + to_write += quote + rec._path + quote; + to_write += "\n"; + _manifest.write(to_write); + } + } else { + // this is just a link. + astring to_write = " Linked to search item "; + to_write += curr_reco.our_link()->_search; + to_write += "\n"; + _manifest.write(to_write); + } + + _manifest.write(astring("END ") + pattern + " */\n\n"); + } + + _manifest.write(footer_string(full_config)); + + // show all the modified files. + if (_modified_files.symbols()) { + const int syms = _modified_files.symbols(); + LOG("Modified Files:"); + LOG("==============="); + for (int i = 0; i < syms; i++) { + LOG(_modified_files.name(i)); + } + } else { + LOG("No files needed modification for generated values."); + } + LOG(""); + + log(time_stamp::notarize(true) + "value_tagger finished.", ALWAYS_PRINT); + + return 0; +} + +#define INBO (indy < scanning.length()) + // a macro that makes length checking less verbose. + +// make sure we drop any spaces in between important bits. +#define SKIP_SPACES \ + while (INBO && parser_bits::white_space(scanning[indy])) indy++; + +// return with a failure but say why it happened. +#define FAIL_PARSE(why) { \ + log(astring("failed to parse the string because ") + why + ".", ALWAYS_PRINT); \ + return false; \ +} + +bool value_tagger::parse_define(const astring &scanning, int indy, + astring &name, int &value, astring &description, int &num_start, + int &num_end) +{ + // prepare our result objects. + name = ""; value = -1; description = ""; num_start = -1; num_end = -1; + + SKIP_SPACES; + + // look for starting parenthesis. + if (!INBO || (scanning[indy] != '(') ) + FAIL_PARSE("the first parenthesis is missing"); + + indy++; // skip paren. + SKIP_SPACES; + + // find the name of the item being defined. + while (INBO && (scanning[indy] != ',') ) { + name += scanning[indy]; + indy++; + } + + indy++; // skip the comma. + SKIP_SPACES; + + astring num_string; + num_start = indy; + while (INBO && parser_bits::is_numeric(scanning[indy])) { + num_string += scanning[indy]; + indy++; + } + num_end = indy - 1; + value = num_string.convert(0); + + SKIP_SPACES; + + if (!INBO || (scanning[indy] != ',') ) + FAIL_PARSE("the post-value comma is missing"); + + indy++; + SKIP_SPACES; + + if (!INBO || (scanning[indy] != '"') ) + FAIL_PARSE("the opening quote for the description is missing"); + + indy++; // now we should be at raw text. + + // scan through the full description, taking into account that it might + // be broken across multiple lines as several quoted bits. + bool in_quote = true; // we're inside a quote now. + while (INBO && (scanning[indy] != ')') ) { + const char curr = scanning[indy]; +//hmmm: escaped quotes are not currently handled. + if (curr == '"') in_quote = !in_quote; // switch quoting state. + else if (in_quote) description += curr; + indy++; + } + + return scanning[indy] == ')'; +} + +bool value_tagger::process_file(const astring &path) +{ + byte_filer examining(path, "rb"); + if (!examining.good()) { + log(astring("Error reading file: ") + path, ALWAYS_PRINT); + return false; + } + examining.seek(0, byte_filer::FROM_END); + int fsize = int(examining.tell()); + examining.seek(0, byte_filer::FROM_START); + + astring contents('\0', fsize + 20); + int bytes_read = examining.read((abyte *)contents.access(), fsize); + // read the file directly into a big astring. + examining.close(); + contents[bytes_read] = '\0'; + contents.shrink(); // drop any extra stuff at end. + + bool modified = false; // set to true if we need to write the file back. + + // check if the file matches our phrases of interest. + bool matched = false; + for (int q = 0; q < _search_list.symbols(); q++) { + search_record &curr_reco = _search_list[q]; + if (contents.contains(curr_reco._search)) { +//_manifest.write(astring("MATCH-") + curr_pattern + ": " + path + "\n" ); //temp + matched = true; + break; + } + } + + if (!matched) return true; + + // now we have verified that there's something interesting in this file. + // go through to find the interesting bits. + + // we do this in the search ordering that we established earlier, so we + // will tag the values in the proper order. + for (int x = 0; x < _search_ordering.length(); x++) { + int q = _search_ordering[x]; // get our real index. + search_record &curr_reco = _search_list[q]; + const astring &curr_pattern = curr_reco._search; +///log(astring("now seeking ") + curr_pattern); + int start_from = 0; // where searches will start from. + + while (true) { + // search forward for next match. + int indy = contents.find(curr_pattern, start_from); + if (negative(indy)) break; // no more matches. + start_from = indy + 5; // ensure we'll skip past the last match. + + // make sure our deadly pattern isn't in front; we don't want to + // process the actual definition of the macro in question. +//log(a_sprintf("indy=%d [indy-1]=%c [indy-2]=%c", indy, contents[indy-1], contents[indy-2])); + if ( (indy > 3) && (contents[indy-1] == ' ') + && (contents[indy-2] == 'e') ) { + int def_indy = contents.find("#define", indy, true); +//log(astring("checking ") + curr_pattern + a_sprintf(": defindy %d, ", def_indy) + path + "\n" ); + + if (non_negative(def_indy) && (absolute_value(indy - def_indy) < 12) ) { + // they're close enough that we probably need to skip this + // occurrence of our search term. +//_manifest.write(astring("DEMATCH-") + curr_pattern + ": had the #define! " + path + "\n" ); + continue; + } + } + + // make sure we don't include commented lines in consideration. + int comm_indy = contents.find("//", indy, true); + if (non_negative(comm_indy)) { +//log("found a comment marker"); + // we found a comment before the definition, but we're not sure how + // far before. + if (absolute_value(comm_indy - indy) < LONGEST_SEPARATION) { +//log("comment is close enough..."); + // they could be on the same line... unless lines are longer than + // our constant. + bool found_cr = false; + for (int q = comm_indy; q < indy; q++) { + if (parser_bits::is_eol(contents[q])) { + found_cr = true; + break; + } + } + if (!found_cr) { + // if there's a comment before the definition and no carriage + // returns in between, then this is just a comment. +//log(astring("DEMATCH-") + curr_pattern + ": had the comment! " + path + "\n" ); + continue; + } + } + } + + // now we are pretty sure this is a righteous definition of an outcome, + // and not the definition of the macro itself. + int value, num_start, num_end; + astring name, description; + bool found_it = parse_define(contents, indy + curr_pattern.length(), + name, value, description, num_start, num_end); + if (!found_it) { + log(astring("there was a problem parsing ") + curr_pattern + " in " + path, ALWAYS_PRINT); + continue; + } + + // handle the special keyword for changing the value. this is useful + // if you want a set of outcomes to start at a specific range. + if (name.equal_to(SKIP_VALUE_PHRASE)) { + LOG(astring("\tSkipping value for ") + curr_pattern + + a_sprintf(" to %d because of request in\n\t", value) + path); + curr_reco.current_value() = value; + } + while (true) { + // make sure that the current value is not already in use. + if (!curr_reco.out_of_band().member(curr_reco.current_value())) + break; + // if we had a match above, we need to adjust the current value. + curr_reco.current_value() += curr_reco.value_increment(); + } + if (name.equal_to(SKIP_VALUE_PHRASE)) { + continue; // keep going now that we vetted the current value. + } + +//must catch some conditions here for values: +// for incrementing types, we can always just try to use the next value +// once we know it wasn't already defined out of band? +// for non-incrementing types, we need to ensure we haven't already seen +// the thing. do we just always add a value seen to out of band? +// for mixed types, the incrementing side needs to not reuse out of band +// values. + + astring other_place; // the other place it was defined. + if (curr_reco.out_of_band().member(value) && curr_reco._no_modify) { + // this is bad; we have already seen this value elsewhere... + for (int x = 0; x < curr_reco.definitions().symbols(); x++) { + // see if we can find the previous definition in our list. + if (value == curr_reco.definitions()[x]._value) + other_place = curr_reco.definitions()[x]._path; + } + non_continuable_error(class_name(), path, + a_sprintf("There is a duplicate value here for %s=%d ! " + "Also defined in %s.", name.s(), value, other_place.s())); + } + + // we care sometimes that this value is different than the next + // sequential one we'd assign. if it's a non-modifying type of + // search, then we can't change the assigned value anyway--we can + // only report the error in re-using a value (above). + if (!curr_reco._no_modify) { + // check that the defined value matches the next one we'd assign. + if (value != curr_reco.current_value()) { + // patch the value with the appropriate one we've been tracking. + modified = true; + value = curr_reco.current_value(); + contents.zap(num_start, num_end); // remove old fusty value. + contents.insert(num_start, a_sprintf("%d", value)); + _modified_files.add(path, ""); + } + // move the current value up (or down). + curr_reco.current_value() += curr_reco.value_increment(); + } else { + // non-modifying type of value here. +//anything to do? + } + + curr_reco.out_of_band() += value; + // we've vetted the value, and now we're definitely using it. + + // make sure they aren't trying to reuse the name for this item. + item_record rec; + bool found_name = false; // we don't want to find name already there. + if (curr_reco.definitions().find(name)) { + rec = *curr_reco.definitions().find(name); + found_name = true; + } + if (found_name) { + // this is bad. this means we are not unique. remove the manifest + // file due to this error. + _manifest.close(); // close the file since we want to whack it. + filename(_manifest_filename).unlink(); + non_continuable_error(class_name(), path, + a_sprintf("There is a duplicate name here (%s)! " + "Also defined in %s.", name.s(), rec._path.s())); + } + + // record the definition in the appropriate table. + curr_reco.definitions().add(name, item_record(name, value, + description, path, curr_reco._tag)); + +//log(curr_pattern + a_sprintf(": name=%s value=%d desc=[%s]\n", name.s(), value, description.s())); + + } + } + + if (modified) { + // rewrite the file, since we modified its contents. + bool chmod_result = filename(path).chmod(filename::ALLOW_BOTH, + filename::USER_RIGHTS); +/* + int chmod_value; +#ifdef __UNIX__ + chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; +#elif defined(__WIN32__) + chmod_value = _S_IREAD | _S_IWRITE; +#else + //unknown. let's try unix... + chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; +#endif + int chmod_result = chmod(path.s(), chmod_value); +*/ + if (!chmod_result) { + log(astring("there was a problem changing permissions on ") + path + + "; writing the new version might fail.", ALWAYS_PRINT); + } + + byte_filer rewriting(path, "wb"); + rewriting.write(contents); + rewriting.close(); + } + + return true; +} + +bool value_tagger::process_tree(const astring &path) +{ + directory_tree dir(path, "*.h"); + if (!dir.good()) return false; + + dir_tree_iterator *ted = dir.start(directory_tree::prefix); + // create our iterator to perform a prefix traversal. + + filename curr_dir; // the current path the iterator is at. + string_array files; // the filenames held at the iterator. + + while (directory_tree::current(*ted, curr_dir, files)) { + // we have a good directory to process. + + // omit any subdirectories that exactly match directories we've already + // scanned. necessary to avoid redoing whole areas. + if (!_dirs_seen.find(curr_dir)) { + // deal with each matching header file we've found. + for (int i = 0; i < files.length(); i++) { + bool file_ret = process_file(filename(curr_dir.raw(), files[i])); + if (!file_ret) { + log(astring("There was an error while processing ") + files[i], ALWAYS_PRINT); + } + } + + _dirs_seen.add(curr_dir, ""); + } + + // go to the next place. + directory_tree::next(*ted); + } + + directory_tree::throw_out(ted); + return true; +} + +HOOPLE_MAIN(value_tagger, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/clam_tools/version.ini b/nucleus/tools/clam_tools/version.ini new file mode 100644 index 00000000..1ae20aa5 --- /dev/null +++ b/nucleus/tools/clam_tools/version.ini @@ -0,0 +1,6 @@ +[version] +description = CLAM Tools for Building +root = clamtools +name = CLAM Tools +extension = exe + diff --git a/nucleus/tools/clam_tools/version_stamper.cpp b/nucleus/tools/clam_tools/version_stamper.cpp new file mode 100644 index 00000000..a668a9f0 --- /dev/null +++ b/nucleus/tools/clam_tools/version_stamper.cpp @@ -0,0 +1,131 @@ +/*****************************************************************************\ +* * +* Name : version_stamper * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef LOG +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger(), to_print) + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace versions; + +//! This class creates resource information for applications and libraries to be version stamped. +/*! + This creates a resource (.rc) file and a C++ header (.h) file when given the directory where + a version information file (version.ini) resides. It creates the files in that directory. +*/ + +class version_stamper : public application_shell +{ +public: + version_stamper(); + ~version_stamper(); + + DEFINE_CLASS_NAME("version_stamper"); + + int execute(); + // performs the main action of creating resource and code files. +}; + +////////////// + +version_stamper::version_stamper() : application_shell() +{ +} + +version_stamper::~version_stamper() {} + +int version_stamper::execute() +{ +/// FUNCDEF("execute"); + SETUP_CONSOLE_LOGGER; // override the file_logger from app_shell. + if (application::_global_argc < 2) { + log(astring("The directory where the 'version.ini' file is located\n" + "must be specified as the first parameter of this program. Another\n" + "version file may optionally be specified as the second parameter of\n" + "the program; the version contained in this file will be used to set\n" + "the version of the file specified in the first parameter.\n" + "Additionally, if the environment variable 'DEBUG' exists, then the\n" + "generated RC file will be marked as a debug build. Otherwise it is\n" + "marked as a release build. Note that the CLAM system automatically\n" + "sets this for you.\n\n"), ALWAYS_PRINT); + return 1; + } + + astring path_name = application::_global_argv[1]; + astring source_version_file; // blank by default. + if (application::_global_argc > 2) + source_version_file = application::_global_argv[2]; + bool ret = version_ini::one_stop_version_stamp(path_name, source_version_file, true); + if (!ret) return 1; // failure. + return 0; // success. +} + +HOOPLE_MAIN(version_stamper, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/clam_tools/vsts_version_fixer.cpp b/nucleus/tools/clam_tools/vsts_version_fixer.cpp new file mode 100644 index 00000000..8fc8d3cd --- /dev/null +++ b/nucleus/tools/clam_tools/vsts_version_fixer.cpp @@ -0,0 +1,363 @@ +/*****************************************************************************\ +* * +* Name : vsts_version_fixer * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 2008-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef LOG +#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s) +#undef BASE_LOG +#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT) + +using namespace application; +//using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; +using namespace timely; +using namespace versions; + +//#define DEBUG_VSTS_VERSION_FIXER + // uncomment for noisy version. + +//////////////////////////////////////////////////////////////////////////// + +class vsts_version_fixer : public application::application_shell +{ +public: + vsts_version_fixer() : application_shell() {} + virtual ~vsts_version_fixer() {} + + virtual int execute(); + + DEFINE_CLASS_NAME("vsts_version_fixer"); + + void remove_confusing_files(); + //!< tosses out the generated files that confuse ms devstudio. + +//move these + typedef bool spider_method(const directory ¤t); + //!< prototype for functions that are called during directory spidering. + /*!< this function should do whatever work is needed on the items in + that "current" directory. true should be returned by this method when + the traversal of the directory is still desired. if there is a reason + to stop traversing the directory hierarchy, then it should return false. */ + +//hmmm: support postfix and in order also. +//hmmm: support reporting where the spidering stopped. + bool spider_directory(directory start, spider_method to_invoke); + //!< traverses hierarchy "start" in prefix order while calling "to_invoke". + /*!< true is returned if all invoked spider methods returned true. + otherwise, false is returned. */ +//move those + + bool perform_version_stamping(const filename &start_name); + //!< finds all version ini files and applies stamps using them. + + void whack_in_subdirs(const directory &start, + const string_array &file_whacks, const string_array &dir_whacks); + //!< recursively cleans all items found in "file_whacks" and "dir_whacks". + /*!< "file_whacks" is a list of file suffixes to whack. for example, to + remove all files matching a pattern *.exe, pass in just ".exe" in the + "file_whacks". the "dir_whacks" list is a list of directories to + completely obliterate where found. */ +}; + +HOOPLE_MAIN(vsts_version_fixer, ) + +//////////////////////////////////////////////////////////////////////////// + +//hmmm: move to a useful place; maybe even in directory class? +bool vsts_version_fixer::spider_directory(directory start, + spider_method to_invoke) +{ +// FUNCDEF("spider_directory"); + + using namespace basis; + +//LOG(astring("spider_directory: ") + start.path()); + // call our method on this directory first. this ensures that we have + // dealt with it before we spider off elsewhere. + bool ret = to_invoke(start); + if (!ret) return false; // bail. + + // now let's look at the subdirectories. we'll recurse on all of them in + // the order listed. + const string_array &dirs = start.directories(); +//LOG(astring("dirs found to spider: ") + dirs.text_form()); + for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) { + const astring ¤t_dir = dirs[dir_indy]; +//LOG(astring("currdir into ") + current_dir); + if (current_dir.equal_to(".svn")) continue; // skip this. + if (current_dir.equal_to("CVS")) continue; // skip this also. + directory new_dir(start.path() + "/" + current_dir, start.pattern().observe()); + bool ret = spider_directory(new_dir, to_invoke); + if (!ret) return false; // bail from subdir issue. + } + // if we made it to here, everything was groovy. + return true; +} + +//////////////////////////////////////////////////////////////////////////// + +#define static_class_name() "vsts_version_fixer" + +// global variables used to communicate with whacking_spider. +string_array global_file_whacks; +string_array global_dir_whacks; + +bool whacking_spider(const directory ¤t) +{ +// FUNCDEF("whacking_spider"); + using namespace basis; +//LOG(astring("whacking_spider: ") + current.path()); + // iterate across the files in the directory and check for evil ones. + const string_array &files = current.files(); + for (int file_indy = 0; file_indy < files.length(); file_indy++) { + const astring ¤t_file = files[file_indy]; +//LOG(astring("currfile ") + current_file); + // now iterate across our pattern list to see if this thing is + // one of the offending files. + for (int pat_indy = 0; pat_indy < global_file_whacks.length(); pat_indy++) { +//LOG(astring("currpat ") + global_file_whacks[pat_indy]); + if (current_file.iends(global_file_whacks[pat_indy])) { + filename goner(current.path() + "/" + current_file); + BASE_LOG(astring("whack file: ") + goner.raw()); + goner.unlink(); + break; // stop looking at the pattern list for matches. + } + } + } + + // okay, now that we've cleaned out those files, let's look at the + // subdirectories. + const string_array &dirs = current.directories(); + for (int dir_indy = 0; dir_indy < dirs.length(); dir_indy++) { + const astring ¤t_dir = dirs[dir_indy]; +//LOG(astring("currdir ") + current_dir); + for (int pat_indy = 0; pat_indy < global_dir_whacks.length(); pat_indy++) { + if (current_dir.iequals(global_dir_whacks[pat_indy])) { + filename goner(current.path() + "/" + current_dir); + BASE_LOG(astring("whack dir: ") + goner.raw()); +//hmmm: plug in recursive delete here instead. +basis::un_int kid; +launch_process::run("rm", astring("-rf ") + goner.raw(), launch_process::AWAIT_APP_EXIT, kid); + break; // skip remainder of patterns for this dir. + } + } + } + return true; +} + +#undef static_class_name + +//////////////////////////////////////////////////////////////////////////// + +void vsts_version_fixer::whack_in_subdirs(const directory &start, + const string_array &file_whacks, const string_array &dir_whacks) +{ + FUNCDEF("whack_in_subdirs"); + using namespace basis; + + // save the lists so the spider method can see them. + // note that this approach with a global variable would be bad if there + // were concurrent invocations of the spidering, but we're not doing + // that here. + global_file_whacks = file_whacks; + global_dir_whacks = dir_whacks; + + bool worked = spider_directory(start, whacking_spider); + if (!worked) { + LOG(astring("spidering of ") + start.path() + " failed for some reason."); + } +} + +//////////////////////////////////////////////////////////////////////////// + +#define static_class_name() "vsts_version_fixer" + +basis::astring global_build_ini; + +bool stamping_spider(const directory ¤t) +{ +// FUNCDEF("stamping_spider"); + using namespace basis; +//LOG(astring("stamping_spider: ") + current.path()); + + const string_array &files = current.files(); + for (int file_indy = 0; file_indy < files.length(); file_indy++) { + const astring ¤t_file = files[file_indy]; +//LOG(astring("currfile ") + current_file); + // we won't process the "core_version.ini" file, which is a special + // case that is somewhat well known as not being a file used (by us) + // for dlls. + if (current_file.ends("version.ini") + && !current_file.iequals("core_version.ini") ) { +//LOG(astring("found ver file: ") + current.path() + "/" + current_file); + version_ini::one_stop_version_stamp(current.path() + "/" + current_file, + global_build_ini, true); + } + } + return true; +} + +#undef static_class_name + +//////////////////////////////////////////////////////////////////////////// + +bool vsts_version_fixer::perform_version_stamping(const filename &start_name) +{ +// FUNCDEF("perform_version_stamping"); + directory start(start_name); + return spider_directory(start, stamping_spider); +} + +//////////////////////////////////////////////////////////////////////////// + +void vsts_version_fixer::remove_confusing_files() +{ + using namespace basis; + // clean out a few directories that show up in the source tree from c# + // projects compilation. c# projects always rebuild every time anyways, + // so this doesn't lose us any compilation time. the only thing c# + // projects don't ever seem to rebuild is their version resource, unless + // they're forced to totally recompile like we cause below. + string_array source_file_whacks; // none right now. + string_array source_dir_whacks; + source_dir_whacks += "obj"; + source_dir_whacks += "Debug"; + source_dir_whacks += "Release"; + source_dir_whacks += "bin"; + source_dir_whacks += "temp_build"; + directory repo_source(environment::get("REPOSITORY_DIR") + "/source"); + whack_in_subdirs(repo_source, source_file_whacks, source_dir_whacks); + directory libra_src(environment::get("REPOSITORY_DIR") + "/libraries"); + whack_in_subdirs(libra_src, source_file_whacks, source_dir_whacks); + directory produ_src(environment::get("REPOSITORY_DIR") + "/products"); + whack_in_subdirs(produ_src, source_file_whacks, source_dir_whacks); + +/* this never helped. + // clean out a variety of bad files in the objects hierarchy. + // currently this is just the generated RES files which we have seen cause + // vsts to think apps and dlls are up to date when they are actually not. + directory repo_objects(environment::get("REPOSITORY_DIR")); + string_array objects_file_whacks; + objects_file_whacks += ".res"; + string_array objects_dir_whacks; // none right now. + whack_in_subdirs(repo_objects, objects_file_whacks, objects_dir_whacks); +*/ +} + +int vsts_version_fixer::execute() +{ + FUNCDEF("execute"); + using namespace basis; + log(time_stamp::notarize(true) + "vsts_version_fixer started.", ALWAYS_PRINT); + + remove_confusing_files(); + + astring repo_dir = environment::get("REPOSITORY_DIR"); + + // figure out which build parameter file to use. + global_build_ini = ""; + astring parmfile = environment::get("BUILD_PARAMETER_FILE"); + if (parmfile.t()) { + global_build_ini = parmfile; +LOG(astring("found parm variable ") + parmfile); + } else { + // they didn't specify the file. argh. + global_build_ini = repo_dir + "/production/feisty_meow_config.ini"; + if (!filename(global_build_ini).exists()) { +LOG(astring("guess not found: ") + global_build_ini); + LOG("cannot locate the build configuration file."); + return 3; + } + } + + // now stamp versions on everything we can find. + filename repo_source = repo_dir + "/../../libraries"; + if (!repo_source.exists()) { + repo_source = repo_dir + "/source"; + if (!repo_source.exists()) { + LOG("cannot locate the main library source location."); + return 3; + } + } +LOG(astring("chose source dir as ") + repo_source); + perform_version_stamping(repo_source); + + filename repo_apps = repo_dir + "/../../products"; + if (repo_apps.exists()) { + perform_version_stamping(repo_apps); + } + log(time_stamp::notarize(true) + "vsts_version_fixer finished.", ALWAYS_PRINT); + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/clam_tools/write_build_config.cpp b/nucleus/tools/clam_tools/write_build_config.cpp new file mode 100644 index 00000000..53cfa0a4 --- /dev/null +++ b/nucleus/tools/clam_tools/write_build_config.cpp @@ -0,0 +1,434 @@ +/*****************************************************************************\ +* * +* Name : write_build_config * +* Author : Chris Koeritz * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include "write_build_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace configuration; +using namespace filesystem; +using namespace loggers; +using namespace structures; +using namespace textual; +using namespace versions; + +const int MAX_LINE_SIZE = 2048; + //!< we should never see an ini line longer than this. + +const int MAX_HEADER_FILE = 128 * KILOBYTE; + //!< an excessively long allowance for the maximum generated header size. + +const char *DEFINITIONS_STATEMENT = "DEFINITIONS"; + //!< the tag we see in the config file for directly compatible macros. + +const char *EXPORT_STATEMENT = "export "; + //!< a tag we see on variables to be inherited by subshells + +// make conditionals that we will eat. +const char *IFEQ_STATEMENT = "ifeq"; +const char *IFNEQ_STATEMENT = "ifneq"; +const char *ENDIF_STATEMENT = "endif"; + +#undef LOG +#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) + +write_build_config::write_build_config() +: application_shell(), + _end_matter(new astring), + _ver(new version), + _nesting(0) +{} + +write_build_config::~write_build_config() +{ + WHACK(_end_matter); + WHACK(_ver); +} + +const string_set &write_build_config::exclusions() +{ + static string_set _hidden; + static bool _initted = false; + if (!_initted) { + _hidden += "DEBUG"; + _hidden += "OPTIMIZE"; + _hidden += "STRICT_WARNINGS"; + } + return _hidden; +} + +// adds some more material to dump at the end of the file. +#define ADD_COMMENT_RETURN(sym, val) { \ + *_end_matter += astring(" ") + sym + " = " + val + "\n"; \ + return common::OKAY; \ +} + +outcome write_build_config::output_macro(const astring &symbol, + const astring &value, astring &accumulator) +{ + // drop any excluded items to avoid interfering with devstu solution. + if (exclusions().member(symbol)) + ADD_COMMENT_RETURN(symbol, value); + // drop any malformed symbols or values. + if (symbol.contains("\"") || value.contains("\"")) + ADD_COMMENT_RETURN(symbol, value); + accumulator += " #ifndef "; + accumulator += symbol; + accumulator += "\n"; + accumulator += " #define "; + accumulator += symbol; + accumulator += " \""; + accumulator += value; + accumulator += "\"\n"; + accumulator += " #endif\n"; + return common::OKAY; +} + +bool write_build_config::process_version_parts(const astring &symbol, + const astring &value) +{ + if (symbol.equal_to("major")) + { _ver->set_component(version::MAJOR, value); return true; } + if (symbol.equal_to("minor")) + { _ver->set_component(version::MINOR, value); return true; } + if (symbol.equal_to("revision")) + { _ver->set_component(version::REVISION, value); return true; } + if (symbol.equal_to("build")) + { _ver->set_component(version::BUILD, value); return true; } + return false; +} + +bool write_build_config::check_nesting(const astring &to_check) +{ + if (to_check.compare(IFEQ_STATEMENT, 0, 0, int(strlen(IFEQ_STATEMENT)), true) + || to_check.compare(IFNEQ_STATEMENT, 0, 0, int(strlen(IFNEQ_STATEMENT)), true)) { + _nesting++; + return true; + } + if (to_check.compare(ENDIF_STATEMENT, 0, 0, int(strlen(ENDIF_STATEMENT)), true)) { + _nesting--; + return true; + } + return false; +} + +outcome write_build_config::output_decorated_macro(const astring &symbol_in, + const astring &value, astring &cfg_accumulator, astring &ver_accumulator) +{ + // make sure we catch any conditionals. + if (check_nesting(symbol_in)) + ADD_COMMENT_RETURN(symbol_in, value); + // toss out any exclusions. + if (exclusions().member(symbol_in)) + ADD_COMMENT_RETURN(symbol_in, value); + if (symbol_in.contains("\"") || value.contains("\"")) + ADD_COMMENT_RETURN(symbol_in, value); + if (symbol_in[0] == '[') + ADD_COMMENT_RETURN(symbol_in, value); + if (_nesting) + ADD_COMMENT_RETURN(symbol_in, value); + // switch the output stream based on whether its a version component or not. + astring *the_accumulator = &cfg_accumulator; + if (process_version_parts(symbol_in, value)) { + the_accumulator = &ver_accumulator; + } + // add a special tag so that we won't be colliding with other names. + astring symbol = astring("__build_") + symbol_in; + return output_macro(symbol, value, *the_accumulator); +} + +outcome write_build_config::output_definition_macro + (const astring &embedded_value, astring &accumulator) +{ +// FUNCDEF("output_definition_macro"); +//LOG(astring("into output def with: ") + embedded_value); + variable_tokenizer t; + t.parse(embedded_value); + if (!t.symbols()) + ADD_COMMENT_RETURN("bad definition", embedded_value); + if (exclusions().member(t.table().name(0))) + ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]); + if (_nesting) + ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]); + return output_macro(t.table().name(0), t.table()[0], accumulator); +} + +bool write_build_config::write_output_file(const astring &filename, + const astring &new_contents) +{ + FUNCDEF("write_output_file"); + // now read the soon-to-be output file so we can check its current state. + bool write_header = true; + byte_filer check_header(filename, "rb"); + if (check_header.good()) { + byte_array file_contents; + int read = check_header.read(file_contents, MAX_HEADER_FILE); +if (read < 1) LOG("why is existing header contentless?"); + if (read > 0) { + astring found(astring::UNTERMINATED, (char *)file_contents.observe(), + read); +//LOG(astring("got existing content:\n-----\n") + found + "\n-----\n"); +//LOG(astring("new_content has:\n-----\n") + new_contents + "\n-----\n"); + if (found == new_contents) { + write_header = false; + } + } + } + // writing only occurs when we know that the build configurations have + // changed. if the file is the same, we definitely don't want to write + // it because pretty much all the other files depend on it. + if (write_header) { + // we actually want to blast out a new file. + byte_filer build_header(filename, "wb"); + if (!build_header.good()) + non_continuable_error(static_class_name(), func, astring("failed to create " + "build header file in ") + build_header.filename()); + build_header.write(new_contents); + LOG(astring(static_class_name()) + ": wrote config to " + + build_header.filename()); + } else { + // nothing has changed. +// LOG(astring(static_class_name()) + ": config already up to date in " +// + filename); + } + return true; +} + +int write_build_config::execute() +{ + FUNCDEF("execute"); + SETUP_CONSOLE_LOGGER; // override the file_logger from app_shell. + + // find our build ini file. + astring repodir = environment::get("REPOSITORY_DIR"); + + // the below code should never be needed for a properly configured build. +#ifdef __WIN32__ + if (!repodir) repodir = "l:"; +#else // unix and other locations. + if (!repodir) + repodir = environment::get("HOME") + "/hoople"; +#endif + + astring fname; + // where we seek out our build settings. + astring parmfile = environment::get("BUILD_PARAMETER_FILE"); + if (parmfile.t()) fname = parmfile; + else fname = repodir + "/build.ini"; + + // find our storage area for the build headers. we know a couple build + // configurations by now, but this should really be coming out of a config + // file instead. + astring library_directory = repodir + "/source/lib_src/library"; + if (!filename(library_directory).good()) { + library_directory = repodir + "/source/core/library"; + if (!filename(library_directory).good()) { + library_directory = repodir + "/libraries/library"; + if (!filename(library_directory).good()) { + library_directory = repodir + "/../../libraries/library"; + if (!filename(library_directory).good()) { + non_continuable_error(static_class_name(), func, + astring("failed to locate the library folder storing the generated files.")); + } + } + } + } + + // these are very specific paths, but they really are where we expect to + // see the headers. + + astring cfg_header_filename = library_directory + "/" + "__build_configuration.h"; + astring ver_header_filename = library_directory + "/" + "__build_version.h"; + + // open the ini file for reading. + byte_filer ini(fname, "r"); + if (!ini.good()) + non_continuable_error(static_class_name(), func, astring("failed to open " + "build configuration file for reading at ") + ini.filename()); +//hmmm: parameterize the build ini thing above! + + // now we build strings that represents the output files we want to create. + astring cfg_accumulator; + astring ver_accumulator; + + // odd indentation below comes from the strings being c++ code that will be + // written to a file. +//hmmm: another location to fix!!! + cfg_accumulator += "\ +#ifndef BUILD_SYSTEM_CONFIGURATION\n\ +#define BUILD_SYSTEM_CONFIGURATION\n\n\ + // This file provides all of the code flags which were used when this build\n\ + // was generated. Some of the items in the build configuration have been\n\ + // stripped out because they are not used.\n\n"; + ver_accumulator += "\ +#ifndef BUILD_VERSION_CONFIGURATION\n\ +#define BUILD_VERSION_CONFIGURATION\n\n\ + // This file provides the version macros for this particular build.\n\n"; + + // iterate through the entries we read in earlier and generate our header. + astring symbol, value; + astring buffer; + while (!ini.eof()) { + int chars = ini.getline(buffer, MAX_LINE_SIZE); + if (!chars) continue; // hmmm. + + variable_tokenizer t; + t.parse(buffer); + if (!t.symbols()) continue; // not so good. + + // pull out the first pair we found and try to parse it. + symbol = t.table().name(0); + value = t.table()[0]; + symbol.strip_spaces(astring::FROM_BOTH_SIDES); + + // clean out + characters that can come from += declarations. + while ( (symbol[symbol.end()] == '+') || (symbol[symbol.end()] == ':') ) { + symbol.zap(symbol.end(), symbol.end()); + symbol.strip_spaces(astring::FROM_END); + } + + if (symbol[0] == '#') continue; // toss out any comments. + + if (!symbol) continue; // seems like that one didn't work out so well. + + if (symbol.compare(EXPORT_STATEMENT, 0, 0, int(strlen(EXPORT_STATEMENT)), true)) { + // clean out export statements in front of our variables. + symbol.zap(0, int(strlen(EXPORT_STATEMENT) - 1)); + } + + // check for a make-style macro definition. + if (symbol.compare(DEFINITIONS_STATEMENT, 0, 0, int(strlen(DEFINITIONS_STATEMENT)), true)) { + // found a macro definition. write that up after pulling the real + // contents out. + output_definition_macro(value, cfg_accumulator); + } else { + // this one is hopefully a very tasty specialized macro. we will + // show it with added text to make it unique. + output_decorated_macro(symbol, value, cfg_accumulator, ver_accumulator); + } + } + + // write some calculated macros now. + ver_accumulator += "\n"; + ver_accumulator += " // calculated macros are dropped in here.\n\n"; + + // we write our version in a couple forms. hopefully we accumulated it. + + // this one is the same as the file version currently (below), but may go to + // a different version at some point. + ver_accumulator += " #define __build_SYSTEM_VERSION \""; + ver_accumulator += _ver->flex_text_form(); + ver_accumulator += "\"\n\n"; + + // we drop in the version as numbers also, since the version RC wants that. + ver_accumulator += " #define __build_FILE_VERSION_COMMAS "; + ver_accumulator += _ver->flex_text_form(version::COMMAS); + ver_accumulator += "\n"; + // another form of the file version for dotted notation. + ver_accumulator += " #define __build_FILE_VERSION \""; + ver_accumulator += _ver->flex_text_form(); + ver_accumulator += "\"\n"; + + // product version is just the first two parts. + _ver->set_component(version::REVISION, "0"); + _ver->set_component(version::BUILD, "0"); + // product version as a list of numbers. + ver_accumulator += " #define __build_PRODUCT_VERSION_COMMAS "; + ver_accumulator += _ver->flex_text_form(version::COMMAS); + ver_accumulator += "\n"; + // another form of the product version for use as a string. + ver_accumulator += " #define __build_PRODUCT_VERSION \""; + ver_accumulator += _ver->flex_text_form(version::DOTS, version::MINOR); + ver_accumulator += "\"\n"; + + // write a blob of comments at the end with what we didn't use. + cfg_accumulator += "\n"; + cfg_accumulator += "/*\n"; + cfg_accumulator += "These settings were not used:\n"; + cfg_accumulator += *_end_matter; + cfg_accumulator += "*/\n"; + cfg_accumulator += "\n"; + cfg_accumulator += "#endif /* outer guard */\n\n"; + + // finish up the version file also. + ver_accumulator += "\n"; + ver_accumulator += "#endif /* outer guard */\n\n"; + + if (!write_output_file(cfg_header_filename, cfg_accumulator)) { + LOG(astring("failed writing output file ") + cfg_header_filename); + } + if (!write_output_file(ver_header_filename, ver_accumulator)) { + LOG(astring("failed writing output file ") + ver_header_filename); + } + + return 0; +} + +HOOPLE_MAIN(write_build_config, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/clam_tools/write_build_config.h b/nucleus/tools/clam_tools/write_build_config.h new file mode 100644 index 00000000..3efa4b99 --- /dev/null +++ b/nucleus/tools/clam_tools/write_build_config.h @@ -0,0 +1,82 @@ +#ifndef BUILD_DEFAULTS_CLASS +#define BUILD_DEFAULTS_CLASS + +/*****************************************************************************\ +* * +* Name : write_build_config * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* This class creates a header file that will provide macros that govern * +* how the build is created under win32 using visual studio project files. * +* This file is not on other platforms, nor with the clam makefile system. * +* * +******************************************************************************* +* Copyright (c) 1995-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +class write_build_config : public application::application_shell +{ +public: + write_build_config(); + ~write_build_config(); + + DEFINE_CLASS_NAME("write_build_config"); + + int execute(); + //!< performs the main action of creating our build configuration header. + + const structures::string_set &exclusions(); + //!< returns the set of symbols that we will not include in the header. + + basis::outcome output_macro(const basis::astring &symbol, const basis::astring &value, + basis::astring &accumulator); + //!< sends a macro definition for "symbol" with "value" to "accumulator". + + basis::outcome output_decorated_macro(const basis::astring &symbol, const basis::astring &value, + basis::astring &cfg_accumulator, basis::astring &ver_accumulator); + //!< produces a new macro by adding a uniquifying string to "symbol". + /*!< if the item is a version component, it will be output to the + "ver_accumulator" but otherwise it goes to the "cfg_accumulator". */ + + basis::outcome output_definition_macro(const basis::astring &embedded_value, + basis::astring &accumulator); + //!< parses a 'name=value' pair out of "embedded_value" and writes a macro. + + bool process_version_parts(const basis::astring &symbol, const basis::astring &value); + //!< checks on "symbol" to see if it's a version component. stores if so. + /*!< if the string was a version component, then true is returned. */ + + bool check_nesting(const basis::astring &to_check); + //!< if "to_check" is a make conditional, the nesting level is adjusted. + /*!< also if it's a conditional, true is returned, which means the line + can be dropped. */ + + bool write_output_file(const basis::astring &filename, const basis::astring &contents); + //!< writes "contents" to "filename" if it differs from current contents. + +private: + basis::astring *_end_matter; // stuff that isn't part of the real file. + structures::version *_ver; // accumulated when we see the right parts. + int _nesting; // how many levels of conditionals do we see? + + // not provided. + write_build_config(const write_build_config &); + write_build_config &operator =(const write_build_config &); +}; + +#endif + diff --git a/nucleus/tools/dependency_tool/Xfuncproto.h b/nucleus/tools/dependency_tool/Xfuncproto.h new file mode 100644 index 00000000..ffbbe905 --- /dev/null +++ b/nucleus/tools/dependency_tool/Xfuncproto.h @@ -0,0 +1,88 @@ +/* $XConsortium: Xfuncproto.h,v 1.8 94/04/17 20:10:49 rws Exp $ */ +/* + * +Copyright (c) 1989, 1991 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + * + */ + +/* Definitions to make function prototypes manageable */ + +#ifndef _XFUNCPROTO_H_ +#define _XFUNCPROTO_H_ + +#ifndef NeedFunctionPrototypes +#if defined(FUNCPROTO) || __STDC__ || defined(__cplusplus) || defined(c_plusplus) +#define NeedFunctionPrototypes 1 +#else +#define NeedFunctionPrototypes 0 +#endif +#endif /* NeedFunctionPrototypes */ + +#ifndef NeedVarargsPrototypes +#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&2) +#define NeedVarargsPrototypes 1 +#else +#define NeedVarargsPrototypes 0 +#endif +#endif /* NeedVarargsPrototypes */ + +#if NeedFunctionPrototypes + +#ifndef NeedNestedPrototypes +#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&8) +#define NeedNestedPrototypes 1 +#else +#define NeedNestedPrototypes 0 +#endif +#endif /* NeedNestedPrototypes */ + +#ifndef _Xconst +#if __STDC__ || defined(__cplusplus) || defined(c_plusplus) || (FUNCPROTO&4) +#define _Xconst const +#else +#define _Xconst +#endif +#endif /* _Xconst */ + +#ifndef NeedWidePrototypes +#ifdef NARROWPROTO +#define NeedWidePrototypes 0 +#else +#define NeedWidePrototypes 1 /* default to make interropt. easier */ +#endif +#endif /* NeedWidePrototypes */ + +#endif /* NeedFunctionPrototypes */ + +#ifndef _XFUNCPROTOBEGIN +#ifdef __cplusplus /* for C++ V2.0 */ +#define _XFUNCPROTOBEGIN extern "C" { /* do not leave open across includes */ +#define _XFUNCPROTOEND } +#else +#define _XFUNCPROTOBEGIN +#define _XFUNCPROTOEND +#endif +#endif /* _XFUNCPROTOBEGIN */ + +#endif /* _XFUNCPROTO_H_ */ diff --git a/nucleus/tools/dependency_tool/Xos2defs.h b/nucleus/tools/dependency_tool/Xos2defs.h new file mode 100644 index 00000000..6e4b1324 --- /dev/null +++ b/nucleus/tools/dependency_tool/Xos2defs.h @@ -0,0 +1,74 @@ +/* $XConsortium: Xw32defs.h,v 1.4 94/02/25 18:50:11 rws Exp $ */ + +#error + +#ifndef _XW32DEFS_H +#define _XW32DEFS_H + +typedef char *caddr_t; + +#define access _access +#define chdir _chdir +#define chmod _chmod +#define close _close +#define creat _creat +#define dup _dup +#define dup2 _dup2 +#define environ _environ +#define execl _execl +#define execle _execle +#define execlp _execlp +#define execlpe _execlpe +#define execv _execv +#define execve _execve +#define execvp _execvp +#define execvpe _execvpe +#define fdopen _fdopen +#define fileno _fileno +#define fstat _fstat +#define getcwd _getcwd +#define getpid _getpid +#define hypot _hypot +#define isatty _isatty +#define lseek _lseek +#define mkdir _mkdir +#define mktemp _mktemp +#define open _open +#define putenv _putenv +#define read _read +#define rmdir _rmdir +#define sleep(x) _sleep((x) * 1000) +#define stat _stat +#define sys_errlist _sys_errlist +#define sys_nerr _sys_nerr +#define umask _umask +#define unlink _unlink +#define write _write + +/* +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_RDWR _O_RDWR +#define O_APPEND _O_APPEND +#define O_CREAT _O_CREAT +#define O_TRUNC _O_TRUNC +#define O_EXCL _O_EXCL +#define O_TEXT _O_TEXT +#define O_BINARY _O_BINARY +#define O_RAW _O_BINARY + +#define S_IFMT _S_IFMT +#define S_IFDIR _S_IFDIR +#define S_IFCHR _S_IFCHR +#define S_IFREG _S_IFREG +#define S_IREAD _S_IREAD +#define S_IWRITE _S_IWRITE +#define S_IEXEC _S_IEXEC + +*/ +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + +#endif diff --git a/nucleus/tools/dependency_tool/Xosdefs.h b/nucleus/tools/dependency_tool/Xosdefs.h new file mode 100644 index 00000000..4513bd90 --- /dev/null +++ b/nucleus/tools/dependency_tool/Xosdefs.h @@ -0,0 +1,126 @@ +/* + * O/S-dependent (mis)feature macro definitions + * + * $XConsortium: Xosdefs.h,v 1.14 94/11/30 20:48:05 kaleb Exp $ + * $XFree86: xc/include/Xosdefs.h,v 3.7 1995/01/28 15:42:05 dawes Exp $ + * +Copyright (c) 1991 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + */ + +#ifndef _XOSDEFS_H_ +#define _XOSDEFS_H_ + +/* + * X_NOT_STDC_ENV means does not have ANSI C header files. Lack of this + * symbol does NOT mean that the system has stdarg.h. + * + * X_NOT_POSIX means does not have POSIX header files. Lack of this + * symbol does NOT mean that the POSIX environment is the default. + * You may still have to define _POSIX_SOURCE to get it. + */ + +#ifdef NOSTDHDRS +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif + +#ifdef sony +#if !defined(SYSTYPE_SYSV) && !defined(_SYSTYPE_SYSV) +#define X_NOT_POSIX +#endif +#endif + +#ifdef UTEK +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif + +#ifdef vax +#ifndef ultrix /* assume vanilla BSD */ +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif +#endif + +#ifdef luna +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif + +#ifdef Mips +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif + +#ifdef USL +#ifdef SYSV /* (release 3.2) */ +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif +#endif + +#ifdef i386 +#ifdef SYSV +#ifndef SCO +#define X_NOT_POSIX +#endif +#define X_NOT_STDC_ENV +#endif +#endif + +#ifdef MOTOROLA +#ifdef SYSV +#define X_NOT_STDC_ENV +#endif +#endif + +#ifdef sun +#ifdef SVR4 +/* define this to whatever it needs to be */ +#define X_POSIX_C_SOURCE 199300L +#endif +#endif + +#ifdef WIN32 +#ifndef _POSIX_ +#define X_NOT_POSIX +#endif +#endif + +#ifdef __OS2__ +#ifndef _POSIX_ +#define X_NOT_POSIX +#endif +#endif + +#if defined(nec_ews_svr2) || defined(SX) || defined(PC_UX) +#define X_NOT_POSIX +#define X_NOT_STDC_ENV +#endif + +#ifdef __EMX__ +#define USGISH +#endif + +#endif /* _XOSDEFS_H_ */ diff --git a/nucleus/tools/dependency_tool/Xw32defs.h b/nucleus/tools/dependency_tool/Xw32defs.h new file mode 100644 index 00000000..58c2187c --- /dev/null +++ b/nucleus/tools/dependency_tool/Xw32defs.h @@ -0,0 +1,72 @@ +/* $XConsortium: Xw32defs.h,v 1.4 94/02/25 18:50:11 rws Exp $ */ + +#ifndef _XW32DEFS_H +#define _XW32DEFS_H + +typedef char *caddr_t; + +#define access _access +#define alloca _alloca +#define chdir _chdir +#define chmod _chmod +#define close _close +#define creat _creat +#define dup _dup +#define dup2 _dup2 +#define environ _environ +#define execl _execl +#define execle _execle +#define execlp _execlp +#define execlpe _execlpe +#define execv _execv +#define execve _execve +#define execvp _execvp +#define execvpe _execvpe +#define fdopen _fdopen +#define fileno _fileno +#define fstat _fstat +#define getcwd _getcwd +#define getpid _getpid +#define hypot _hypot +#define isascii __isascii +#define isatty _isatty +#define lseek _lseek +#define mkdir _mkdir +#define mktemp _mktemp +#define open _open +#define putenv _putenv +#define read _read +#define rmdir _rmdir +#define sleep(x) _sleep((x) * 1000) +#define stat _stat +#define sys_errlist _sys_errlist +#define sys_nerr _sys_nerr +#define umask _umask +#define unlink _unlink +#define write _write + +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_RDWR _O_RDWR +#define O_APPEND _O_APPEND +#define O_CREAT _O_CREAT +#define O_TRUNC _O_TRUNC +#define O_EXCL _O_EXCL +#define O_TEXT _O_TEXT +#define O_BINARY _O_BINARY +#define O_RAW _O_BINARY + +#define S_IFMT _S_IFMT +#define S_IFDIR _S_IFDIR +#define S_IFCHR _S_IFCHR +#define S_IFREG _S_IFREG +#define S_IREAD _S_IREAD +#define S_IWRITE _S_IWRITE +#define S_IEXEC _S_IEXEC + +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + +#endif diff --git a/nucleus/tools/dependency_tool/cppsetup.cpp b/nucleus/tools/dependency_tool/cppsetup.cpp new file mode 100644 index 00000000..dc404113 --- /dev/null +++ b/nucleus/tools/dependency_tool/cppsetup.cpp @@ -0,0 +1,230 @@ +/* $XConsortium: cppsetup.c,v 1.13 94/04/17 20:10:32 gildea Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "def.h" + +#ifdef CPP + +#include "ifparser.h" + +/* + * This file is strictly for the sake of cpy.y and yylex.c (if + * you indeed have the source for cpp). + */ +#define IB 1 +#define SB 2 +#define NB 4 +#define CB 8 +#define QB 16 +#define WB 32 +#define SALT '#' +#if pdp11 | vax | ns16000 | mc68000 | ibm032 +#define COFF 128 +#else +#define COFF 0 +#endif +/* + * These variables used by cpy.y and yylex.c + */ +extern char *outp, *inp, *newp, *pend; +extern char *ptrtab; +extern char fastab[]; +extern char slotab[]; + +/* + * cppsetup + */ +struct filepointer *currentfile; +inclist *currentinc; + +int cppsetup(register char *line, register struct filepointer *filep, + register inclist *inc) +{ + register char *p, savec; + static bool setupdone = false; + bool value; + + if (!setupdone) { + cpp_varsetup(); + setupdone = true; + } + + currentfile = filep; + currentinc = inc; + inp = newp = line; + for (p=newp; *p; p++) + ; + + /* + * put a newline back on the end, and set up pend, etc. + */ + *p++ = '\n'; + savec = *p; + *p = '\0'; + pend = p; + + ptrtab = slotab+COFF; + *--inp = SALT; + outp=inp; + value = yyparse(); + *p = savec; + return(value); +} + +struct symtab *lookup(char *symbol) +{ + static struct symtab undefined; + struct symtab *sp; + + sp = isdefined(symbol, currentinc, NULL); + if (sp == NULL) { + sp = &undefined; + sp->s_value = NULL; + } + return (sp); +} + +int pperror(int tag, int x0, int x1, int x2, int x3, int x4) +{ + warning("\"%s\", line %d: ", currentinc->i_file, currentfile->f_line); + warning(x0,x1,x2,x3,x4); +} + + +int yyerror(register char *s) +{ + fatalerr("Fatal error: %s\n", s); +} + +#else /* not CPP */ + +#include "ifparser.h" + +#include + +struct _parse_data { + struct filepointer *filep; + inclist *inc; + const char *line; +}; + +static const char *_my_if_errors(IfParser *ip, const char *cp, + const char *expecting) +{ + struct _parse_data *pd = (struct _parse_data *) ip->data; + int lineno = pd->filep->f_line; + char *filename = pd->inc->i_file; + char prefix[300]; + int prefixlen; + int i; + + sprintf (prefix, "\"%s\":%d", filename, lineno); + prefixlen = int(strlen(prefix)); + fprintf (stderr, "%s: %s", prefix, pd->line); + i = int(cp - pd->line); + if (i > 0 && pd->line[i-1] != '\n') { + putc ('\n', stderr); + } + for (i += prefixlen + 3; i > 0; i--) { + putc (' ', stderr); + } + fprintf (stderr, "^--- expecting %s\n", expecting); + return NULL; +} + + +#define MAXNAMELEN 256 + +static struct symtab *_lookup_variable(IfParser *ip, const char *var, int len) +{ + char tmpbuf[MAXNAMELEN + 1]; + struct _parse_data *pd = (struct _parse_data *) ip->data; + + if (len > MAXNAMELEN) + return 0; + + strncpy (tmpbuf, var, len); + tmpbuf[len] = '\0'; + return isdefined (tmpbuf, pd->inc, NULL); +} + + +static int _my_eval_defined(IfParser *ip, const char *var, int len) +{ + if (_lookup_variable (ip, var, len)) return 1; + else return 0; +} + +#define isvarfirstletter(ccc) (isalpha(ccc) || (ccc) == '_') + +static int _my_eval_variable(IfParser *ip, const char *var, int len) +{ + struct symtab *s; + + s = _lookup_variable (ip, var, len); + if (!s) + return 0; + do { + var = s->s_value; + if (!isvarfirstletter(*var)) + break; + s = _lookup_variable (ip, var, int(strlen(var))); + } while (s); + + return atoi(var); +} + + +int cppsetup(register char *line, register struct filepointer *filep, + register inclist *inc) +{ + IfParser ip; + struct _parse_data pd; + int val = 0; + + pd.filep = filep; + pd.inc = inc; + pd.line = line; + ip.funcs.handle_error = _my_if_errors; + ip.funcs.eval_defined = _my_eval_defined; + ip.funcs.eval_variable = _my_eval_variable; + ip.data = (char *) &pd; + + (void) ParseIfExpression (&ip, line, &val); + if (val) + return IF; + else + return IFFALSE; +} + +#endif /* CPP */ + diff --git a/nucleus/tools/dependency_tool/def.h b/nucleus/tools/dependency_tool/def.h new file mode 100644 index 00000000..de4e4189 --- /dev/null +++ b/nucleus/tools/dependency_tool/def.h @@ -0,0 +1,195 @@ +/* $XConsortium: def.h,v 1.25 94/04/17 20:10:33 gildea Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#include +#include "Xosdefs.h" +#ifdef WIN32 +#endif +#ifdef __OS2__ +#define __STDC__ 1 +#include "Xos2defs.h" +#endif +#include "Xfuncproto.h" +#include +#ifndef X_NOT_POSIX +#ifndef _POSIX_SOURCE +#define _POSIX_SOURCE +#endif +#endif +#include +#include +#include + +#define MAXDEFINES 512 +#define MAXFILES 4000 +#define MAXDIRS 640 +#define SYMTABINC 10 /* must be > 1 for define() to work right */ + +/* the following must match the directives table in main.c */ +#define IF 0 +#define IFDEF 1 +#define IFNDEF 2 +#define ELSE 3 +#define ENDIF 4 +#define DEFINE 5 +#define UNDEF 6 +#define INCLUDE 7 +#define LINE 8 +#define PRAGMA 9 +#define ERROR 10 +#define IDENT 11 +#define SCCS 12 +#define ELIF 13 +#define EJECT 14 +#define IMPORT 15 + +#define IFFALSE 16 /* pseudo value --- never matched */ +#define ELIFFALSE 17 /* pseudo value --- never matched */ +#define INCLUDEDOT 18 /* pseudo value --- never matched */ +#define IFGUESSFALSE 19 /* pseudo value --- never matched */ +#define ELIFGUESSFALSE 20 /* pseudo value --- never matched */ + +///#define DEBUG + // uncomment this for debugging version. + +#ifdef DEBUG +extern int _debugmask; +/* + * debug levels are: + * + * 0 show ifn*(def)*,endif + * 1 trace defined/!defined + * 2 show #include + * 3 show #include SYMBOL + * 4-6 unused + */ +#define debug(level,arg) { if (_debugmask & (1 << level)) warning arg; } +#else +#define debug(level,arg) {} +#endif /* DEBUG */ + +struct symtab { + char *s_name; + char *s_value; +}; + +struct inclist { + char *i_incstring; /* string from #include line */ + char *i_file; /* path name of the include file */ + inclist **i_list; /* list of files it itself includes */ + int i_listlen; /* length of i_list */ + symtab *i_defs; /* symbol table for this file */ + int i_ndefs; /* current # defines */ + int i_deflen; /* amount of space in table */ + bool i_defchecked; /* whether defines have been checked */ + bool i_notified; /* whether we have revealed includes */ + bool i_marked; /* whether it's in the makefile */ + bool i_searched; /* whether we have read this */ + bool i_included_sym; /* whether #include SYMBOL was found */ + /* Can't use i_list if true */ +}; + +struct filepointer { + char *f_p; + char *f_base; + char *f_end; + long f_len; + long f_line; + char *f_name; +}; + +#ifndef X_NOT_STDC_ENV +#include +#if defined(macII) && !defined(__STDC__) /* stdlib.h fails to define these */ +char *malloc(), *realloc(); +#endif /* macII */ +#else +char *malloc(); +char *realloc(); +#endif + +/* +char *getline(); +symtab *slookup(); +symtab *isdefined(); +symtab *fdefined(); +filepointer *getfile(); +inclist *newinclude(register char *, register char *); +inclist *inc_path(); +*/ + +// cppsetup.cpp: +int cppsetup(register char *line, register filepointer *filep, + register inclist *inc); + +// include.cpp +inclist *newinclude(register char *newfile, register char *incstring); +void inc_clean(); +inclist *inc_path(register char *file, register char *include, bool dot, + bool &failure_okay); +void included_by(register inclist *ip, register inclist *newfile); + +// main.cpp: +char *base_name(register char *file); +char *copy(register char *str); +filepointer *getfile(char *file); +void freefile(filepointer *fp); +char *getline(register filepointer *filep); +int match(register const char *str, register const char **list); +void redirect(char *line, char *makefile); +#if NeedVarargsPrototypes + void fatalerr(const char *, ...); + void warning(const char *, ...); + void warning1(const char *, ...); +#endif + +// parse.cpp: +void define(char *def, inclist *file); +void define2(char *name, char *val, inclist *file); +int deftype(register char *line, register filepointer *filep, + register inclist *file_red, register inclist *file, + int parse_it); +symtab *fdefined(register char *symbol, inclist *file, inclist **srcfile); +int find_includes(filepointer *filep, inclist *file, + inclist *file_red, int recursion, bool failOK); +int gobble(register filepointer *filep, inclist *file, + inclist *file_red); +symtab *isdefined(register char *symbol, inclist *file, + inclist **srcfile); +symtab *slookup(register char *symbol, register inclist *file); +void undefine(char *symbol, register inclist *file); +int zero_value(register char *exp, register filepointer *filep, + register inclist *file_red); + +// pr.cpp: +void add_include(filepointer *filep, inclist *file, + inclist *file_red, char *include, bool dot, bool failOK); +void pr(register inclist *ip, char *file, char *base, bool rc_file); +void recursive_pr_include(register inclist *head, register char *file, + register char *base); + diff --git a/nucleus/tools/dependency_tool/ifparser.cpp b/nucleus/tools/dependency_tool/ifparser.cpp new file mode 100644 index 00000000..b4de6824 --- /dev/null +++ b/nucleus/tools/dependency_tool/ifparser.cpp @@ -0,0 +1,401 @@ +/* + * $XConsortium: ifparser.c,v 1.7 94/01/18 21:30:50 rws Exp $ + * + * Copyright 1992 Network Computing Devices, Inc. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Network Computing Devices may not be + * used in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. Network Computing Devices makes + * no representations about the suitability of this software for any purpose. + * It is provided ``as is'' without express or implied warranty. + * + * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, + * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * Author: Jim Fulton + * Network Computing Devices, Inc. + * + * Simple if statement processor + * + * This module can be used to evaluate string representations of C language + * if constructs. It accepts the following grammar: + * + * EXPRESSION := VALUE + * | VALUE BINOP EXPRESSION + * + * VALUE := '(' EXPRESSION ')' + * | '!' VALUE + * | '-' VALUE + * | 'defined' '(' variable ')' + * | 'defined' variable + * | # variable '(' variable-list ')' + * | variable + * | number + * + * BINOP := '*' | '/' | '%' + * | '+' | '-' + * | '<<' | '>>' + * | '<' | '>' | '<=' | '>=' + * | '==' | '!=' + * | '&' | '|' + * | '&&' | '||' + * + * The normal C order of precidence is supported. + * + * + * External Entry Points: + * + * ParseIfExpression parse a string for #if + */ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "ifparser.h" + +#include +#include +#include + +/**************************************************************************** + Internal Macros and Utilities for Parser + ****************************************************************************/ + +#define DO(val) if (!(val)) return NULL +#define CALLFUNC(ggg,fff) (*((ggg)->funcs.fff)) +#define SKIPSPACE(ccc) while (isspace(*ccc)) ccc++ +#define isvarfirstletter(ccc) (isalpha(ccc) || (ccc) == '_') + + +static const char *parse_variable(IfParser *g, const char *cp, + const char **varp) +{ + SKIPSPACE (cp); + + if (!isvarfirstletter (*cp)) + return CALLFUNC(g, handle_error) (g, cp, "variable name"); + + *varp = cp; + /* EMPTY */ + for (cp++; isalnum(*cp) || *cp == '_'; cp++) ; + return cp; +} + + +static const char *parse_number(IfParser *g, const char *cp, int *valp) +{ + SKIPSPACE (cp); + + if (!isdigit(*cp)) + return CALLFUNC(g, handle_error) (g, cp, "number"); + +#ifdef WIN32 + char *hold_result; + *valp = strtol(cp, &hold_result, 0); + cp = hold_result; +#else + *valp = atoi (cp); + /* EMPTY */ + for (cp++; isdigit(*cp); cp++) ; +#endif + return cp; +} + + +static const char *parse_value(IfParser *g, const char *cp, int *valp) +{ + const char *var; + + *valp = 0; + + SKIPSPACE (cp); + if (!*cp) + return cp; + + switch (*cp) { + case '(': + DO (cp = ParseIfExpression (g, cp + 1, valp)); + SKIPSPACE (cp); + if (*cp != ')') + return CALLFUNC(g, handle_error) (g, cp, ")"); + + return cp + 1; /* skip the right paren */ + + case '!': + DO (cp = parse_value (g, cp + 1, valp)); + *valp = !(*valp); + return cp; + + case '-': + DO (cp = parse_value (g, cp + 1, valp)); + *valp = -(*valp); + return cp; + + case '#': + DO (cp = parse_variable (g, cp + 1, &var)); + SKIPSPACE (cp); + if (*cp != '(') + return CALLFUNC(g, handle_error) (g, cp, "("); + do { + DO (cp = parse_variable (g, cp + 1, &var)); + SKIPSPACE (cp); + } while (*cp && *cp != ')'); + if (*cp != ')') + return CALLFUNC(g, handle_error) (g, cp, ")"); + *valp = 1; /* XXX */ + return cp + 1; + + case 'd': + if (strncmp (cp, "defined", 7) == 0 && !isalnum(cp[7])) { + int paren = 0; + cp += 7; + SKIPSPACE (cp); + if (*cp == '(') { + paren = 1; + cp++; + } + DO (cp = parse_variable (g, cp, &var)); + SKIPSPACE (cp); + if (paren && *cp != ')') + return CALLFUNC(g, handle_error) (g, cp, ")"); + *valp = (*(g->funcs.eval_defined)) (g, var, int(cp - var)); + return cp + paren; /* skip the right paren */ + } + /* fall out */ + } + + if (isdigit(*cp)) { + DO (cp = parse_number (g, cp, valp)); + } else if (!isvarfirstletter(*cp)) + return CALLFUNC(g, handle_error) (g, cp, "variable or number"); + else { + DO (cp = parse_variable (g, cp, &var)); + *valp = (*(g->funcs.eval_variable)) (g, var, int(cp - var)); + } + + return cp; +} + +static const char *parse_product(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_value (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '*': + DO (cp = parse_product (g, cp + 1, &rightval)); + *valp = (*valp * rightval); + break; + + case '/': + DO (cp = parse_product (g, cp + 1, &rightval)); + *valp = (*valp / rightval); + break; + + case '%': + DO (cp = parse_product (g, cp + 1, &rightval)); + *valp = (*valp % rightval); + break; + } + return cp; +} + +static const char *parse_sum(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_product (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '+': + DO (cp = parse_sum (g, cp + 1, &rightval)); + *valp = (*valp + rightval); + break; + + case '-': + DO (cp = parse_sum (g, cp + 1, &rightval)); + *valp = (*valp - rightval); + break; + } + return cp; +} + + +static const char *parse_shift(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_sum (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '<': + if (cp[1] == '<') { + DO (cp = parse_shift (g, cp + 2, &rightval)); + *valp = (*valp << rightval); + } + break; + + case '>': + if (cp[1] == '>') { + DO (cp = parse_shift (g, cp + 2, &rightval)); + *valp = (*valp >> rightval); + } + break; + } + return cp; +} + +static const char *parse_inequality(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_shift (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '<': + if (cp[1] == '=') { + DO (cp = parse_inequality (g, cp + 2, &rightval)); + *valp = (*valp <= rightval); + } else { + DO (cp = parse_inequality (g, cp + 1, &rightval)); + *valp = (*valp < rightval); + } + break; + + case '>': + if (cp[1] == '=') { + DO (cp = parse_inequality (g, cp + 2, &rightval)); + *valp = (*valp >= rightval); + } else { + DO (cp = parse_inequality (g, cp + 1, &rightval)); + *valp = (*valp > rightval); + } + break; + } + return cp; +} + +static const char *parse_equality(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_inequality (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '=': + if (cp[1] == '=') + cp++; + DO (cp = parse_equality (g, cp + 1, &rightval)); + *valp = (*valp == rightval); + break; + + case '!': + if (cp[1] != '=') + break; + DO (cp = parse_equality (g, cp + 2, &rightval)); + *valp = (*valp != rightval); + break; + } + return cp; +} + +static const char *parse_band(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_equality (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '&': + if (cp[1] != '&') { + DO (cp = parse_band (g, cp + 1, &rightval)); + *valp = (*valp & rightval); + } + break; + } + return cp; +} + + +static const char *parse_bor(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_band (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '|': + if (cp[1] != '|') { + DO (cp = parse_bor (g, cp + 1, &rightval)); + *valp = (*valp | rightval); + } + break; + } + return cp; +} + +static const char *parse_land(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_bor (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '&': + if (cp[1] != '&') + return CALLFUNC(g, handle_error) (g, cp, "&&"); + DO (cp = parse_land (g, cp + 2, &rightval)); + *valp = (*valp && rightval); + break; + } + return cp; +} + +static const char *parse_lor(IfParser *g, const char *cp, int *valp) +{ + int rightval; + + DO (cp = parse_land (g, cp, valp)); + SKIPSPACE (cp); + + switch (*cp) { + case '|': + if (cp[1] != '|') + return CALLFUNC(g, handle_error) (g, cp, "||"); + DO (cp = parse_lor (g, cp + 2, &rightval)); + *valp = (*valp || rightval); + break; + } + return cp; +} + + +/**************************************************************************** + External Entry Points + ****************************************************************************/ + +const char *ParseIfExpression(IfParser *g, const char *cp, int *valp) +{ + return parse_lor (g, cp, valp); +} + + diff --git a/nucleus/tools/dependency_tool/ifparser.h b/nucleus/tools/dependency_tool/ifparser.h new file mode 100644 index 00000000..72fb6a47 --- /dev/null +++ b/nucleus/tools/dependency_tool/ifparser.h @@ -0,0 +1,70 @@ +/* + * $XConsortium: ifparser.h,v 1.1 92/08/22 13:05:39 rws Exp $ + * + * Copyright 1992 Network Computing Devices, Inc. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Network Computing Devices may not be + * used in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. Network Computing Devices makes + * no representations about the suitability of this software for any purpose. + * It is provided ``as is'' without express or implied warranty. + * + * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, + * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * Author: Jim Fulton + * Network Computing Devices, Inc. + * + * Simple if statement processor + * + * This module can be used to evaluate string representations of C language + * if constructs. It accepts the following grammar: + * + * EXPRESSION := VALUE + * | VALUE BINOP EXPRESSION + * + * VALUE := '(' EXPRESSION ')' + * | '!' VALUE + * | '-' VALUE + * | 'defined' '(' variable ')' + * | variable + * | number + * + * BINOP := '*' | '/' | '%' + * | '+' | '-' + * | '<<' | '>>' + * | '<' | '>' | '<=' | '>=' + * | '==' | '!=' + * | '&' | '|' + * | '&&' | '||' + * + * The normal C order of precidence is supported. + * + * + * External Entry Points: + * + * ParseIfExpression parse a string for #if + */ + +#include + +struct IfParser { + struct { /* functions */ + const char *(*handle_error) (IfParser *, const char *, const char *); + int (*eval_variable) (IfParser *, const char *, int); + int (*eval_defined) (IfParser *, const char *, int); + } funcs; + char *data; +}; + +const char *ParseIfExpression(IfParser *, const char *, int *); + diff --git a/nucleus/tools/dependency_tool/imakemdep.h b/nucleus/tools/dependency_tool/imakemdep.h new file mode 100644 index 00000000..d9d2c277 --- /dev/null +++ b/nucleus/tools/dependency_tool/imakemdep.h @@ -0,0 +1,718 @@ +/* $XConsortium: imakemdep.h,v 1.82 95/01/12 16:27:01 kaleb Exp $ */ +/* $XFree86: xc/config/imake/imakemdep.h,v 3.8 1995/01/28 15:40:59 dawes Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + + +/* + * This file contains machine-dependent constants for the imake utility. + * When porting imake, read each of the steps below and add in any necessary + * definitions. In general you should *not* edit ccimake.c or imake.c! + */ + +#ifdef CCIMAKE +/* + * Step 1: imake_ccflags + * Define any special flags that will be needed to get imake.c to compile. + * These will be passed to the compile along with the contents of the + * make variable BOOTSTRAPCFLAGS. + */ +#ifdef hpux +#ifdef hp9000s800 +#define imake_ccflags "-DSYSV" +#else +#define imake_ccflags "-Wc,-Nd4000,-Ns3000 -DSYSV" +#endif +#endif + +#if defined(macII) || defined(_AUX_SOURCE) +#define imake_ccflags "-DmacII -DSYSV" +#endif + +#ifdef stellar +#define imake_ccflags "-DSYSV" +#endif + +#if defined(USL) || defined(Oki) || defined(NCR) +#define imake_ccflags "-Xc -DSVR4" +#endif + +#ifdef sony +#if defined(SYSTYPE_SYSV) || defined(_SYSTYPE_SYSV) +#define imake_ccflags "-DSVR4" +#else +#include +#if NEWSOS < 41 +#define imake_ccflags "-Dbsd43 -DNOSTDHDRS" +#else +#if NEWSOS < 42 +#define imake_ccflags "-Dbsd43" +#endif +#endif +#endif +#endif + +#ifdef _CRAY +#define imake_ccflags "-DSYSV -DUSG" +#endif + +#if defined(_IBMR2) || defined(aix) +#define imake_ccflags "-Daix -DSYSV" +#endif + +#ifdef Mips +# if defined(SYSTYPE_BSD) || defined(BSD) || defined(BSD43) +# define imake_ccflags "-DBSD43" +# else +# define imake_ccflags "-DSYSV" +# endif +#endif + +#ifdef is68k +#define imake_ccflags "-Dluna -Duniosb" +#endif + +#ifdef SYSV386 +# ifdef SVR4 +# define imake_ccflags "-Xc -DSVR4" +# else +# define imake_ccflags "-DSYSV" +# endif +#endif + +#ifdef SVR4 +# ifdef i386 +# define imake_ccflags "-Xc -DSVR4" +# endif +#endif + +#ifdef SYSV +# ifdef i386 +# define imake_ccflags "-DSYSV" +# endif +#endif + +#ifdef __convex__ +#define imake_ccflags "-fn -tm c1" +#endif + +#ifdef apollo +#define imake_ccflags "-DX_NOT_POSIX" +#endif + +#ifdef WIN32 +#define imake_ccflags "-nologo -batch -D__STDC__" +#endif + +#ifdef __uxp__ +#define imake_ccflags "-DSVR4 -DANSICPP" +#endif + +#ifdef __sxg__ +#define imake_ccflags "-DSYSV -DUSG -DNOSTDHDRS" +#endif + +#ifdef sequent +#define imake_ccflags "-DX_NOT_STDC_ENV -DX_NOT_POSIX" +#endif + +#ifdef _SEQUENT_ +#define imake_ccflags "-DSYSV -DUSG" +#endif + +#if defined(SX) || defined(PC_UX) +#define imake_ccflags "-DSYSV" +#endif + +#ifdef nec_ews_svr2 +#define imake_ccflags "-DUSG" +#endif + +#if defined(nec_ews_svr4) || defined(_nec_ews_svr4) || defined(_nec_up) || defined(_nec_ft) +#define imake_ccflags "-DSVR4" +#endif + +#ifdef MACH +#define imake_ccflags "-DNOSTDHDRS" +#endif + +/* this is for OS/2 under EMX. This won't work with DOS */ +#if defined(__EMX__) +#define imake_ccflags "-DBSD43" +#endif + +#else /* not CCIMAKE */ +#ifndef MAKEDEPEND +/* + * Step 2: dup2 + * If your OS doesn't have a dup2() system call to duplicate one file + * descriptor onto another, define such a mechanism here (if you don't + * already fall under the existing category(ies). + */ +#if defined(SYSV) && !defined(_CRAY) && !defined(Mips) && !defined(_SEQUENT_) +#define dup2(fd1,fd2) ((fd1 == fd2) ? fd1 : (close(fd2), \ + fcntl(fd1, F_DUPFD, fd2))) +#endif + + +/* + * Step 3: FIXUP_CPP_WHITESPACE + * If your cpp collapses tabs macro expansions into a single space and + * replaces escaped newlines with a space, define this symbol. This will + * cause imake to attempt to patch up the generated Makefile by looking + * for lines that have colons in them (this is why the rules file escapes + * all colons). One way to tell if you need this is to see whether or not + * your Makefiles have no tabs in them and lots of @@ strings. + */ +#if defined(sun) || defined(SYSV) || defined(SVR4) || defined(hcx) || defined(WIN32) || (defined(AMOEBA) && defined(CROSS_COMPILE)) +#define FIXUP_CPP_WHITESPACE +#endif +#ifdef WIN32 +#define REMOVE_CPP_LEADSPACE +#define INLINE_SYNTAX +#define MAGIC_MAKE_VARS +#endif +#ifdef __minix_vmd +#define FIXUP_CPP_WHITESPACE +#endif + +/* + * Step 4: USE_CC_E, DEFAULT_CC, DEFAULT_CPP + * If you want to use cc -E instead of cpp, define USE_CC_E. + * If use cc -E but want a different compiler, define DEFAULT_CC. + * If the cpp you need is not in /lib/cpp, define DEFAULT_CPP. + */ +#ifdef hpux +#define USE_CC_E +#endif +#ifdef WIN32 +#define USE_CC_E +#define DEFAULT_CC "cl" +#endif +#ifdef apollo +#define DEFAULT_CPP "/usr/lib/cpp" +#endif +#if defined(_IBMR2) && !defined(DEFAULT_CPP) +#define DEFAULT_CPP "/usr/lpp/X11/Xamples/util/cpp/cpp" +#endif +#if defined(sun) && defined(SVR4) +#define DEFAULT_CPP "/usr/ccs/lib/cpp" +#endif +#ifdef __bsdi__ +#define DEFAULT_CPP "/usr/bin/cpp" +#endif +#ifdef __uxp__ +#define DEFAULT_CPP "/usr/ccs/lib/cpp" +#endif +#ifdef __sxg__ +#define DEFAULT_CPP "/usr/lib/cpp" +#endif +#ifdef _CRAY +#define DEFAULT_CPP "/lib/pcpp" +#endif +#if defined(__386BSD__) || defined(__NetBSD__) || defined(__FreeBSD__) +#define DEFAULT_CPP "/usr/libexec/cpp" +#endif +#ifdef MACH +#define USE_CC_E +#endif +#ifdef __minix_vmd +#define DEFAULT_CPP "/usr/lib/cpp" +#endif +#if defined(__EMX__) +/* expects cpp in PATH */ +#define DEFAULT_CPP "cpp" +#endif + +/* + * Step 5: cpp_argv + * The following table contains the flags that should be passed + * whenever a Makefile is being generated. If your preprocessor + * doesn't predefine any unique symbols, choose one and add it to the + * end of this table. Then, do the following: + * + * a. Use this symbol in Imake.tmpl when setting MacroFile. + * b. Put this symbol in the definition of BootstrapCFlags in your + * .cf file. + * c. When doing a make World, always add "BOOTSTRAPCFLAGS=-Dsymbol" + * to the end of the command line. + * + * Note that you may define more than one symbol (useful for platforms + * that support multiple operating systems). + */ + +#define ARGUMENTS 50 /* number of arguments in various arrays */ +char *cpp_argv[ARGUMENTS] = { + "cc", /* replaced by the actual program to exec */ + "-I.", /* add current directory to include path */ +#ifdef unix + "-Uunix", /* remove unix symbol so that filename unix.c okay */ +#endif +#if defined(__386BSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(MACH) +# ifdef __i386__ + "-D__i386__", +# endif +# ifdef __GNUC__ + "-traditional" +# endif +#endif +#ifdef M4330 + "-DM4330", /* Tektronix */ +#endif +#ifdef M4310 + "-DM4310", /* Tektronix */ +#endif +#if defined(macII) || defined(_AUX_SOURCE) + "-DmacII", /* Apple A/UX */ +#endif +#ifdef USL + "-DUSL", /* USL */ +#endif +#ifdef sony + "-Dsony", /* Sony */ +#if !defined(SYSTYPE_SYSV) && !defined(_SYSTYPE_SYSV) && NEWSOS < 42 + "-Dbsd43", +#endif +#endif +#ifdef _IBMR2 + "-D_IBMR2", /* IBM RS-6000 (we ensured that aix is defined above */ +#ifndef aix +#define aix /* allow BOOTSTRAPCFLAGS="-D_IBMR2" */ +#endif +#endif /* _IBMR2 */ +#ifdef aix + "-Daix", /* AIX instead of AOS */ +#ifndef ibm +#define ibm /* allow BOOTSTRAPCFLAGS="-Daix" */ +#endif +#endif /* aix */ +#ifdef ibm + "-Dibm", /* IBM PS/2 and RT under both AOS and AIX */ +#endif +#ifdef luna + "-Dluna", /* OMRON luna 68K and 88K */ +#ifdef luna1 + "-Dluna1", +#endif +#ifdef luna88k /* need not on UniOS-Mach Vers. 1.13 */ + "-traditional", /* for some older version */ +#endif /* instead of "-DXCOMM=\\#" */ +#ifdef uniosb + "-Duniosb", +#endif +#ifdef uniosu + "-Duniosu", +#endif +#endif /* luna */ +#ifdef _CRAY /* Cray */ + "-Ucray", +#endif +#ifdef Mips + "-DMips", /* Define and use Mips for Mips Co. OS/mach. */ +# if defined(SYSTYPE_BSD) || defined(BSD) || defined(BSD43) + "-DBSD43", /* Mips RISCOS supports two environments */ +# else + "-DSYSV", /* System V environment is the default */ +# endif +#endif /* Mips */ +#ifdef MOTOROLA + "-DMOTOROLA", /* Motorola Delta Systems */ +# ifdef SYSV + "-DSYSV", +# endif +# ifdef SVR4 + "-DSVR4", +# endif +#endif /* MOTOROLA */ +#ifdef i386 +# ifdef SVR4 + "-Di386", + "-DSVR4", +# endif +# ifdef SYSV + "-Di386", + "-DSYSV", +# ifdef ISC + "-DISC", +# ifdef ISC40 + "-DISC40", /* ISC 4.0 */ +# else +# ifdef ISC202 + "-DISC202", /* ISC 2.0.2 */ +# else +# ifdef ISC30 + "-DISC30", /* ISC 3.0 */ +# else + "-DISC22", /* ISC 2.2.1 */ +# endif +# endif +# endif +# endif +# ifdef SCO + "-DSCO", +# ifdef SCO324 + "-DSCO324", +# endif +# endif +# endif +# ifdef ESIX + "-Di386", + "-DESIX", +# endif +# ifdef ATT + "-Di386", + "-DATT", +# endif +# ifdef DELL + "-Di386", + "-DDELL", +# endif +#endif +#ifdef SYSV386 /* System V/386 folks, obsolete */ + "-Di386", +# ifdef SVR4 + "-DSVR4", +# endif +# ifdef ISC + "-DISC", +# ifdef ISC40 + "-DISC40", /* ISC 4.0 */ +# else +# ifdef ISC202 + "-DISC202", /* ISC 2.0.2 */ +# else +# ifdef ISC30 + "-DISC30", /* ISC 3.0 */ +# else + "-DISC22", /* ISC 2.2.1 */ +# endif +# endif +# endif +# endif +# ifdef SCO + "-DSCO", +# ifdef SCO324 + "-DSCO324", +# endif +# endif +# ifdef ESIX + "-DESIX", +# endif +# ifdef ATT + "-DATT", +# endif +# ifdef DELL + "-DDELL", +# endif +#endif +#ifdef __osf__ + "-D__osf__", +# ifdef __mips__ + "-D__mips__", +# endif +# ifdef __alpha + "-D__alpha", +# endif +# ifdef __i386__ + "-D__i386__", +# endif +# ifdef __GNUC__ + "-traditional" +# endif +#endif +#ifdef Oki + "-DOki", +#endif +#ifdef sun +#ifdef SVR4 + "-DSVR4", +#endif +#endif +#ifdef WIN32 + "-DWIN32", + "-nologo", + "-batch", + "-D__STDC__", +#endif +#ifdef NCR + "-DNCR", /* NCR */ +#endif +#ifdef linux + "-traditional", + "-Dlinux", +#endif +#ifdef __uxp__ + "-D__uxp__", +#endif +#ifdef __sxg__ + "-D__sxg__", +#endif +#ifdef nec_ews_svr2 + "-Dnec_ews_svr2", +#endif +#ifdef AMOEBA + "-DAMOEBA", +# ifdef CROSS_COMPILE + "-DCROSS_COMPILE", +# ifdef CROSS_i80386 + "-Di80386", +# endif +# ifdef CROSS_sparc + "-Dsparc", +# endif +# ifdef CROSS_mc68000 + "-Dmc68000", +# endif +# else +# ifdef i80386 + "-Di80386", +# endif +# ifdef sparc + "-Dsparc", +# endif +# ifdef mc68000 + "-Dmc68000", +# endif +# endif +#endif +#ifdef __minix_vmd + "-Dminix", +#endif + +#if defined(__EMX__) + "-traditional", + "-Demxos2", +#endif + +}; +#else /* else MAKEDEPEND */ +/* + * Step 6: predefs + * If your compiler and/or preprocessor define any specific symbols, add + * them to the the following table. The definition of struct symtab is + * in util/makedepend/def.h. + */ +struct symtab predefs[] = { +#ifdef apollo + {"apollo", "1"}, +#endif +#ifdef ibm032 + {"ibm032", "1"}, +#endif +#ifdef ibm + {"ibm", "1"}, +#endif +#ifdef aix + {"aix", "1"}, +#endif +#ifdef sun + {"sun", "1"}, +#endif +#ifdef sun2 + {"sun2", "1"}, +#endif +#ifdef sun3 + {"sun3", "1"}, +#endif +#ifdef sun4 + {"sun4", "1"}, +#endif +#ifdef sparc + {"sparc", "1"}, +#endif +#ifdef __sparc__ + {"__sparc__", "1"}, +#endif +#ifdef hpux + {"hpux", "1"}, +#endif +#ifdef __hpux + {"__hpux", "1"}, +#endif +#ifdef __hp9000s800 + {"__hp9000s800", "1"}, +#endif +#ifdef __hp9000s700 + {"__hp9000s700", "1"}, +#endif +#ifdef vax + {"vax", "1"}, +#endif +#ifdef VMS + {"VMS", "1"}, +#endif +#ifdef cray + {"cray", "1"}, +#endif +#ifdef CRAY + {"CRAY", "1"}, +#endif +#ifdef _CRAY + {"_CRAY", "1"}, +#endif +#ifdef att + {"att", "1"}, +#endif +#ifdef mips + {"mips", "1"}, +#endif +#ifdef __mips__ + {"__mips__", "1"}, +#endif +#ifdef ultrix + {"ultrix", "1"}, +#endif +#ifdef stellar + {"stellar", "1"}, +#endif +#ifdef mc68000 + {"mc68000", "1"}, +#endif +#ifdef mc68020 + {"mc68020", "1"}, +#endif +#ifdef __GNUC__ + {(char *)"__GNUC__", (char *)"1"}, +#endif +#if __STDC__ + {(char *)"__STDC__", (char *)"1"}, +#endif +#ifdef __HIGHC__ + {"__HIGHC__", "1"}, +#endif +#ifdef CMU + {"CMU", "1"}, +#endif +#ifdef luna + {"luna", "1"}, +#ifdef luna1 + {"luna1", "1"}, +#endif +#ifdef luna2 + {"luna2", "1"}, +#endif +#ifdef luna88k + {"luna88k", "1"}, +#endif +#ifdef uniosb + {"uniosb", "1"}, +#endif +#ifdef uniosu + {"uniosu", "1"}, +#endif +#endif +#ifdef ieeep754 + {"ieeep754", "1"}, +#endif +#ifdef is68k + {"is68k", "1"}, +#endif +#ifdef m68k + {"m68k", "1"}, +#endif +#ifdef m88k + {"m88k", "1"}, +#endif +#ifdef __m88k__ + {"__m88k__", "1"}, +#endif +#ifdef bsd43 + {"bsd43", "1"}, +#endif +#ifdef hcx + {"hcx", "1"}, +#endif +#ifdef sony + {"sony", "1"}, +#ifdef SYSTYPE_SYSV + {"SYSTYPE_SYSV", "1"}, +#endif +#ifdef _SYSTYPE_SYSV + {"_SYSTYPE_SYSV", "1"}, +#endif +#endif +#ifdef __OSF__ + {"__OSF__", "1"}, +#endif +#ifdef __osf__ + {"__osf__", "1"}, +#endif +#ifdef __alpha + {"__alpha", "1"}, +#endif +#ifdef __DECC + {"__DECC", "1"}, +#endif +#ifdef __decc + {"__decc", "1"}, +#endif +#ifdef __uxp__ + {"__uxp__", "1"}, +#endif +#ifdef __sxg__ + {"__sxg__", "1"}, +#endif +#ifdef _SEQUENT_ + {"_SEQUENT_", "1"}, + {"__STDC__", "1"}, +#endif +#ifdef __bsdi__ + {"__bsdi__", "1"}, +#endif +#ifdef nec_ews_svr2 + {"nec_ews_svr2", "1"}, +#endif +#ifdef nec_ews_svr4 + {"nec_ews_svr4", "1"}, +#endif +#ifdef _nec_ews_svr4 + {"_nec_ews_svr4", "1"}, +#endif +#ifdef _nec_up + {"_nec_up", "1"}, +#endif +#ifdef SX + {"SX", "1"}, +#endif +#ifdef nec + {"nec", "1"}, +#endif +#ifdef _nec_ft + {"_nec_ft", "1"}, +#endif +#ifdef PC_UX + {(char *)"PC_UX", (char *)"1"}, +#endif +#ifdef __EMX__ + {"__EMX__", "1"}, +#endif + /* add any additional symbols before this line */ + {NULL, NULL} +}; + +#endif /* MAKEDEPEND */ +#endif /* CCIMAKE */ diff --git a/nucleus/tools/dependency_tool/include.cpp b/nucleus/tools/dependency_tool/include.cpp new file mode 100644 index 00000000..08ade608 --- /dev/null +++ b/nucleus/tools/dependency_tool/include.cpp @@ -0,0 +1,348 @@ +/* $XConsortium: include.c,v 1.17 94/12/05 19:33:08 gildea Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "def.h" + +#include + +#ifdef _MSC_VER + #undef strcasecmp + #undef strncasecmp + #define strcasecmp strcmpi + #define strncasecmp strnicmp +#endif + +extern inclist inc_list[MAXFILES], *inclistp; +extern char *includedirs[ ]; +extern char *excludedirs[ ]; +extern char *notdotdot[ ]; +extern bool show_where_not; +extern bool warn_multiple; + +// forward. +void remove_dotdot(char *path); +int isdot(register char *p); +int isdotdot(register char *p); +int issymbolic(register char *dir, register char *component); +void included_by(register inclist *ip, register inclist *newfile); + +inclist *inc_path(register char *file, register char *include, bool dot, + bool &failure_okay) +{ + static char path[ BUFSIZ ]; + register char **pp, *p; + register inclist *ip; + struct stat st; + bool found = false; + +//fprintf(stderr, "file=%s include=%s\n", file, include); + const size_t inclen = strlen(include); + if (inclen >= 4) { + register char *cpp_point = include + inclen - 4; + if (!strcasecmp(".cpp", cpp_point)) { + // this is a CPP file include, which we skip. +//fprintf(stderr, "!found match at point: %s\n", cpp_point); +//hold failure_okay = true; +//hold return NULL; + } + } + +////////fprintf(stderr, "incpath entry\n"); + + /* + * Check all previously found include files for a path that + * has already been expanded. + */ + for (ip = inc_list; ip->i_file; ip++) + if ((strcmp(ip->i_incstring, include) == 0) && !ip->i_included_sym) { + found = true; + break; + } + + /* + * If the path was surrounded by "" or is an absolute path, + * then check the exact path provided. + */ + if (!found && (dot || *include == '/' || *include == '\\')) { + if (stat(include, &st) == 0) { + ip = newinclude(include, include); + found = true; + } + else if (show_where_not) + warning1("\tnot in %s\n", include); + } + + /* + * See if this include file is in the directory of the + * file being compiled. + */ + if (!found) { + for (p=file+strlen(file); p>file; p--) + if (*p == '/' || *p == '\\') + break; + if (p == file) + strcpy(path, include); + else { + strncpy(path, file, (p-file) + 1); + path[ (p-file) + 1 ] = '\0'; + strcpy(path + (p-file) + 1, include); + } + remove_dotdot(path); + if (stat(path, &st) == 0) { + ip = newinclude(path, include); + found = true; + } + else if (show_where_not) + warning1("\tnot in %s\n", path); + } + + /* + * Check the include directories specified. (standard include dir + * should be at the end.) + */ + if (!found) + for (pp = includedirs; *pp; pp++) { + sprintf(path, "%s/%s", *pp, include); + remove_dotdot(path); + if (stat(path, &st) == 0) { + register char **pp2; + bool exclude_it = false; + for (pp2 = excludedirs; *pp2; pp2++) { +////////fprintf(stderr, "comparing %s with %s\n", *pp, *pp2); + if (!strncmp(*pp, *pp2, strlen(*pp2))) { + // this file is supposed to be excluded. + exclude_it = true; + break; + } + } +////////if (exclude_it) fprintf(stderr, "excluding path %s\n", path); + if (exclude_it) { + failure_okay = true; + found = false; + } else { + ip = newinclude(path, include); + found = true; + } + break; + } + else if (show_where_not) + warning1("\tnot in %s\n", path); + } + + if (!found) + ip = NULL; + return(ip); +} + +/* + * Occasionally, pathnames are created that look like .../x/../y + * Any of the 'x/..' sequences within the name can be eliminated. + * (but only if 'x' is not a symbolic link!!) + */ +void remove_dotdot(char *path) +{ + register char *end, *from, *to, **cp; + char *components[ MAXFILES ], newpath[ BUFSIZ ]; + bool component_copied; + + /* + * slice path up into components. + */ + to = newpath; + if (*path == '/' || *path == '\\') + *to++ = '/'; + *to = '\0'; + cp = components; + for (from=end=path; *end; end++) + if (*end == '/' || *end == '\\') { + while (*end == '/' || *end == '\\') + *end++ = '\0'; + if (*from) + *cp++ = from; + from = end; + } + *cp++ = from; + *cp = NULL; + + /* + * Recursively remove all 'x/..' component pairs. + */ + cp = components; + while(*cp) { + if (!isdot(*cp) && !isdotdot(*cp) && isdotdot(*(cp+1)) + && !issymbolic(newpath, *cp)) + { + char **fp = cp + 2; + char **tp = cp; + + do + *tp++ = *fp; /* move all the pointers down */ + while (*fp++); + if (cp != components) + cp--; /* go back and check for nested ".." */ + } else { + cp++; + } + } + /* + * Concatenate the remaining path elements. + */ + cp = components; + component_copied = false; + while(*cp) { + if (component_copied) + *to++ = '/'; + component_copied = true; + for (from = *cp; *from; ) + *to++ = *from++; + *to = '\0'; + cp++; + } + *to++ = '\0'; + + /* + * copy the reconstituted path back to our pointer. + */ + strcpy(path, newpath); +} + +int isdot(register char *p) +{ + if(p && *p++ == '.' && *p++ == '\0') + return(true); + return(false); +} + +int isdotdot(register char *p) +{ + if(p && *p++ == '.' && *p++ == '.' && *p++ == '\0') + return(true); + return(false); +} + +int issymbolic(register char *dir, register char *component) +{ +#ifdef S_IFLNK + struct stat st; + char buf[ BUFSIZ ], **pp; + + sprintf(buf, "%s%s%s", dir, *dir ? "/" : "", component); + for (pp=notdotdot; *pp; pp++) + if (strcmp(*pp, buf) == 0) + return (true); + if (lstat(buf, &st) == 0 + && (st.st_mode & S_IFMT) == S_IFLNK) { + *pp++ = copy(buf); + if (pp >= ¬dotdot[ MAXDIRS ]) + fatalerr("out of .. dirs, increase MAXDIRS\n"); + return(true); + } +#endif + return(false); +} + +/* + * Add an include file to the list of those included by 'file'. + */ +inclist *newinclude(register char *newfile, register char *incstring) +{ + register inclist *ip; + + /* + * First, put this file on the global list of include files. + */ + ip = inclistp++; + if (inclistp == inc_list + MAXFILES - 1) + fatalerr("out of space: increase MAXFILES\n"); + ip->i_file = copy(newfile); + ip->i_included_sym = false; + if (incstring == NULL) + ip->i_incstring = ip->i_file; + else + ip->i_incstring = copy(incstring); + + return(ip); +} + +void included_by(register inclist *ip, register inclist *newfile) +{ + register int i; + + if (ip == NULL) + return; + /* + * Put this include file (newfile) on the list of files included + * by 'file'. If 'file' is NULL, then it is not an include + * file itself (i.e. was probably mentioned on the command line). + * If it is already on the list, don't stick it on again. + */ + if (ip->i_list == NULL) + ip->i_list = (inclist **) + malloc(sizeof(inclist *) * ++ip->i_listlen); + else { + for (i=0; ii_listlen; i++) + if (ip->i_list[ i ] == newfile) { + i = int(strlen(newfile->i_file)); + if (!ip->i_included_sym && + !(i > 2 && + newfile->i_file[i-1] == 'c' && + newfile->i_file[i-2] == '.')) + { + /* only complain if ip has */ + /* no #include SYMBOL lines */ + /* and is not a .c file */ + if (warn_multiple) + { + warning("%s includes %s more than once!\n", + ip->i_file, newfile->i_file); + warning1("Already have\n"); + for (i=0; ii_listlen; i++) + warning1("\t%s\n", ip->i_list[i]->i_file); + } + } + return; + } + ip->i_list = (inclist **) realloc(ip->i_list, + sizeof(inclist *) * ++ip->i_listlen); + } + ip->i_list[ ip->i_listlen-1 ] = newfile; +} + +void inc_clean() +{ + register inclist *ip; + + for (ip = inc_list; ip < inclistp; ip++) { + ip->i_marked = false; + } +} + diff --git a/nucleus/tools/dependency_tool/makedep.cpp b/nucleus/tools/dependency_tool/makedep.cpp new file mode 100644 index 00000000..83df3bf1 --- /dev/null +++ b/nucleus/tools/dependency_tool/makedep.cpp @@ -0,0 +1,785 @@ +/* $XConsortium: main.c,v 1.84 94/11/30 16:10:44 kaleb Exp $ */ +/* $XFree86: xc/config/makedepend/main.c,v 3.3 1995/01/28 15:41:03 dawes Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "def.h" + +#ifdef _MSC_VER +#include +#else +#include +#endif +#include +#include + +#ifdef hpux +#define sigvec sigvector +#endif /* hpux */ + +#ifdef X_POSIX_C_SOURCE +#define _POSIX_C_SOURCE X_POSIX_C_SOURCE +#include +#undef _POSIX_C_SOURCE +#else +#if defined(X_NOT_POSIX) || defined(_POSIX_SOURCE) +#include +#else +#define _POSIX_SOURCE +#include +#undef _POSIX_SOURCE +#endif +#endif + +#if NeedVarargsPrototypes +#include +#endif + +#ifdef MINIX +#define USE_CHMOD 1 +#endif + +#ifdef DEBUG +int _debugmask; +#endif + +char *ProgramName; + +const char *directives[] = { + "if", + "ifdef", + "ifndef", + "else", + "endif", + "define", + "undef", + "include", + "line", + "pragma", + "error", + "ident", + "sccs", + "elif", + "eject", + "import", + NULL +}; + +#define MAKEDEPEND +#include "imakemdep.h" /* from config sources */ +#undef MAKEDEPEND + +struct inclist inc_list[ MAXFILES ], *inclistp = inc_list, maininclist; + +char *filelist[ MAXFILES ]; +char *includedirs[ MAXDIRS + 1 ]; +char *excludedirs[ MAXDIRS + 1 ]; +char *notdotdot[ MAXDIRS ]; +char *objprefix = (char *)""; +char *objsuffix = (char *)".obj"; /* OBJSUFFIX; */ +char *startat = (char *)"# DO NOT DELETE"; +int width = 78; +bool append = false; +bool printed = false; +bool verbose = false; +bool show_where_not = false; +bool warn_multiple = false; /* Warn on multiple includes of same file */ + +static +#ifdef SIGNALRETURNSINT +int +#else +void +#endif +c_catch(int sig) +{ + fflush (stdout); + fatalerr ((char *)"got signal %d\n", sig); +} + +#if defined(USG) || (defined(i386) && defined(SYSV)) || defined(WIN32) || defined(__EMX__) || defined(__OS2__) +#define USGISH +#endif + +#ifndef USGISH +#ifndef _POSIX_SOURCE +#define sigaction sigvec +#define sa_handler sv_handler +#define sa_mask sv_mask +#define sa_flags sv_flags +#endif +struct sigaction sig_act; +#endif /* USGISH */ + +/* fatty boombalatty, and wrong idea here. + +// adds any subdirectories under dirname into the list of +// include directories, so we can get a whole hierarchies set of +// include files. +void add_subdirs(char *dirname, char ** &incp) +{ + directory dir(dirname); + string_array subdirs = dir.directories(); + for (int i = 0; i < subdirs.length(); i++) { + istring curr = istring(dirname) + "/" + subdirs[i]; + // add the current subdirectory. + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = strdup(curr.s()); +printf((istring("added ") + curr + "\n").s()); + add_subdirs(curr.s(), incp); + } +} +*/ + +int main(int argc, char **argv) +{ + register char **fp = filelist; + register char **incp = includedirs; + register char **excp = excludedirs; + register char *p; + register struct inclist *ip; + char *makefile = NULL; + struct filepointer *filecontent; + struct symtab *psymp = predefs; + char *endmarker = NULL; + char *defincdir = NULL; + + ProgramName = argv[0]; + + while (psymp->s_name) + { + define2(psymp->s_name, psymp->s_value, &maininclist); + psymp++; + } + if (argc == 2 && argv[1][0] == '@') { + struct stat ast; + int afd; + char *args; + char **nargv; + int nargc; + char quotechar = '\0'; + + nargc = 1; + if ((afd = open(argv[1]+1, O_RDONLY)) < 0) + fatalerr("cannot open \"%s\"\n", argv[1]+1); + fstat(afd, &ast); + args = (char *)malloc(ast.st_size + 2); + if ((ast.st_size = read(afd, args, ast.st_size)) < 0) + fatalerr("failed to read %s\n", argv[1]+1); + args[ast.st_size] = '\n'; + args[ast.st_size + 1] = '\0'; + close(afd); + for (p = args; *p; p++) { + if (quotechar) { + if (quotechar == '\\' || + (*p == quotechar && p[-1] != '\\')) + quotechar = '\0'; + continue; + } + switch (*p) { + case '\\': + case '"': + case '\'': + quotechar = *p; + break; + case ' ': + case '\n': + *p = '\0'; + if (p > args && p[-1]) + nargc++; + break; + } + } + if (p[-1]) + nargc++; + nargv = (char **)malloc(nargc * sizeof(char *)); + nargv[0] = argv[0]; + argc = 1; + for (p = args; argc < nargc; p += strlen(p) + 1) + if (*p) nargv[argc++] = p; + argv = nargv; + } + for(argc--, argv++; argc; argc--, argv++) { + /* if looking for endmarker then check before parsing */ + if (endmarker && strcmp (endmarker, *argv) == 0) { + endmarker = NULL; + continue; + } + if (**argv != '-') { + /* treat +thing as an option for C++ */ + if (endmarker && **argv == '+') + continue; + *fp++ = argv[0]; + continue; + } + switch(argv[0][1]) { + case '-': + endmarker = &argv[0][2]; + if (endmarker[0] == '\0') endmarker = (char *)"--"; + break; + case 'D': + if (argv[0][2] == '\0') { + argv++; + argc--; + } + for (p=argv[0] + 2; *p ; p++) + if (*p == '=') { + *p = ' '; + break; + } + define(argv[0] + 2, &maininclist); + break; + case 'i': + { + char* delim; + char* envinclude; + char* prevdir; + if (endmarker) break; + if (argv[0][2] == '\0') { + argv++; + argc--; + envinclude = getenv(argv[0]); + } else envinclude = getenv(argv[0]+2); + if (!envinclude) break; + prevdir = envinclude; + delim = (char*)strchr(envinclude, ';'); + while(delim) + { + if (incp >= includedirs + MAXDIRS) fatalerr("Too many Include directories.\n"); + *delim = '\0'; + delim++; + *incp++ = prevdir; + prevdir = delim; + delim = (char*)strchr(delim, ';'); + } + } + break; + case 'X': +//fprintf(stderr, "adding Xclude %s\n", argv[0]+2); + // add a directory to the exclusions list. + if (excp >= excludedirs + MAXDIRS) + fatalerr("Too many -X flags.\n"); + *excp++ = argv[0]+2; + if (**(excp-1) == '\0') { + // fix the prior entry, but don't incremement yet; we'll do that + // on the include list instead. + *(excp-1) = *(argv + 1); + } + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags via -X.\n"); + *incp++ = argv[0]+2; + if (**(incp-1) == '\0') { + *(incp-1) = *(++argv); + argc--; + } + break; + case 'I': + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = argv[0]+2; + if (**(incp-1) == '\0') { + *(incp-1) = *(++argv); + argc--; + } +/// add_subdirs(*(incp-1), incp); + break; + case 'Y': + defincdir = argv[0]+2; + break; + /* do not use if endmarker processing */ + case 'a': + if (endmarker) break; + append = true; + break; + case 'w': + if (endmarker) break; + if (argv[0][2] == '\0') { + argv++; + argc--; + width = atoi(argv[0]); + } else + width = atoi(argv[0]+2); + break; + case 'o': + if (endmarker) break; + if (argv[0][2] == '\0') { + argv++; + argc--; + objsuffix = argv[0]; + } else + objsuffix = argv[0]+2; + break; + case 'p': + if (endmarker) break; + if (argv[0][2] == '\0') { + argv++; + argc--; + objprefix = argv[0]; + } else + objprefix = argv[0]+2; + break; + case 'v': + if (endmarker) break; + verbose = true; +#ifdef DEBUG + if (argv[0][2]) + _debugmask = atoi(argv[0]+2); +#endif + break; + case 's': + if (endmarker) break; + startat = argv[0]+2; + if (*startat == '\0') { + startat = *(++argv); + argc--; + } + if (*startat != '#') + fatalerr("-s flag's value should start %s\n", + "with '#'."); + break; + case 'f': + if (endmarker) break; + makefile = argv[0]+2; + if (*makefile == '\0') { + makefile = *(++argv); + argc--; + } + break; + case 'm': + warn_multiple = true; + break; + + /* Ignore -O, -g so we can just pass ${CFLAGS} to + makedepend + */ + case 'O': + case 'g': + break; + default: + if (endmarker) break; + /* fatalerr("unknown opt = %s\n", argv[0]); */ + warning("ignoring option %s\n", argv[0]); + } + } + + if (!defincdir) { +#ifdef PREINCDIR + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = PREINCDIR; +#endif +#ifdef INCLUDEDIR + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = INCLUDEDIR; +#endif +#ifdef POSTINCDIR + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = POSTINCDIR; +#endif + } else if (*defincdir) { + if (incp >= includedirs + MAXDIRS) + fatalerr("Too many -I flags.\n"); + *incp++ = defincdir; + } + + redirect(startat, makefile); + + /* + * c_catch signals. + */ +#ifdef USGISH +/* should really reset SIGINT to SIG_IGN if it was. */ +#ifdef SIGHUP + signal (SIGHUP, c_catch); +#endif + signal (SIGINT, c_catch); +#ifdef SIGQUIT + signal (SIGQUIT, c_catch); +#endif + signal (SIGILL, c_catch); +#ifdef SIGBUS + signal (SIGBUS, c_catch); +#endif + signal (SIGSEGV, c_catch); +#ifdef SIGSYS + signal (SIGSYS, c_catch); +#endif +#else + sig_act.sa_handler = c_catch; +#ifdef _POSIX_SOURCE + sigemptyset(&sig_act.sa_mask); + sigaddset(&sig_act.sa_mask, SIGINT); + sigaddset(&sig_act.sa_mask, SIGQUIT); +#ifdef SIGBUS + sigaddset(&sig_act.sa_mask, SIGBUS); +#endif + sigaddset(&sig_act.sa_mask, SIGILL); + sigaddset(&sig_act.sa_mask, SIGSEGV); + sigaddset(&sig_act.sa_mask, SIGHUP); + sigaddset(&sig_act.sa_mask, SIGPIPE); +#ifdef SIGSYS + sigaddset(&sig_act.sa_mask, SIGSYS); +#endif +#else + sig_act.sa_mask = ((1<<(SIGINT -1)) + |(1<<(SIGQUIT-1)) +#ifdef SIGBUS + |(1<<(SIGBUS-1)) +#endif + |(1<<(SIGILL-1)) + |(1<<(SIGSEGV-1)) + |(1<<(SIGHUP-1)) + |(1<<(SIGPIPE-1)) +#ifdef SIGSYS + |(1<<(SIGSYS-1)) +#endif + ); +#endif /* _POSIX_SOURCE */ + sig_act.sa_flags = 0; + sigaction(SIGHUP, &sig_act, (struct sigaction *)0); + sigaction(SIGINT, &sig_act, (struct sigaction *)0); + sigaction(SIGQUIT, &sig_act, (struct sigaction *)0); + sigaction(SIGILL, &sig_act, (struct sigaction *)0); +#ifdef SIGBUS + sigaction(SIGBUS, &sig_act, (struct sigaction *)0); +#endif + sigaction(SIGSEGV, &sig_act, (struct sigaction *)0); +#ifdef SIGSYS + sigaction(SIGSYS, &sig_act, (struct sigaction *)0); +#endif +#endif /* USGISH */ + + /* + * now peruse through the list of files. + */ + for(fp=filelist; *fp; fp++) { + filecontent = getfile(*fp); + ip = newinclude(*fp, (char *)NULL); + + find_includes(filecontent, ip, ip, 0, false); + freefile(filecontent); + recursive_pr_include(ip, ip->i_file, base_name(*fp)); + inc_clean(); + } + if (printed) + printf("\n"); + + return 0; +} + +struct filepointer *getfile(char *file) +{ + register int fd; + struct filepointer *content; + struct stat st; + + content = (struct filepointer *)malloc(sizeof(struct filepointer)); + content->f_name = strdup(file); + if ((fd = open(file, O_RDONLY)) < 0) { + warning("cannot open \"%s\"\n", file); + content->f_p = content->f_base = content->f_end = (char *)malloc(1); + *content->f_p = '\0'; + return(content); + } + fstat(fd, &st); + content->f_base = (char *)malloc(st.st_size+1); + if (content->f_base == NULL) + fatalerr("cannot allocate mem\n"); + if ((st.st_size = read(fd, content->f_base, st.st_size)) < 0) + fatalerr("failed to read %s\n", file); + close(fd); + content->f_len = st.st_size+1; + content->f_p = content->f_base; + content->f_end = content->f_base + st.st_size; + *content->f_end = '\0'; + content->f_line = 0; + return(content); +} + +void freefile(struct filepointer *fp) +{ + free(fp->f_name); + free(fp->f_base); + free(fp); +} + +char *copy(register char *str) +{ + register char *p = (char *)malloc(strlen(str) + 1); + + strcpy(p, str); + return(p); +} + +int match(register const char *str, register const char **list) +{ + register int i; + + for (i=0; *list; i++, list++) + if (strcmp(str, *list) == 0) + return i; + return -1; +} + +/* + * Get the next line. We only return lines beginning with '#' since that + * is all this program is ever interested in. + */ +char *getline(register struct filepointer *filep) +{ + register char *p, /* walking pointer */ + *eof, /* end of file pointer */ + *bol; /* beginning of line pointer */ + register int lineno; /* line number */ + + eof = filep->f_end; +//// if (p >= eof) return NULL; + lineno = filep->f_line; + bol = filep->f_p; + + // p is always pointing at the "beginning of a line" when we start this loop. + // this means that we must start considering the stuff on that line as + // being a useful thing to look at. + for (p = filep->f_p; p < eof; p++) { +if (bol > p) fatalerr("somehow bol got ahead of p."); + if (*p == '/' && *(p+1) == '*') { + /* consume C-style comments */ + *p++ = ' '; *p++ = ' '; // skip the two we've already seen. + while (p < eof) { + if (*p == '*' && *(p+1) == '/') { + *p++ = ' '; *p = ' '; + // skip one of these last two, let the loop skip the next. + break; + } else if (*p == '\n') lineno++; + p++; // skip the current character. + } + continue; + } else if (*p == '/' && *(p+1) == '/') { + /* consume C++-style comments */ + *p++ = ' '; *p++ = ' '; // skip the comment characters. + while (p < eof && (*p != '\n') ) *p++ = ' '; + // we scan until we get to the end of line. +///no count, since we'll see it again. lineno++; + p--; // skip back to just before \n. + continue; // let the loop skip past the eol that we saw. + } else if (*p == '\\') { + // handle an escape character. + if (*(p+1) == '\n') { + // we modify the stream so we consider the line correctly. + *p = ' '; + *(p+1) = ' '; + lineno++; + } + } else if (*p == '\n') { + // we've finally reached the end of the line. + lineno++; + *p = '\0'; // set the end of line to be a end of string now. + if (bol < p) { + // it's not at the same position as the end of line, so it's worth + // considering. + while ( (bol < p) && ((*bol == ' ') || (*bol == '\t')) ) bol++; +////fprintf(stderr, "%s: %s\n", filep->f_name, bol); +////fflush(stderr); + if (*bol == '#') { + register char *cp; + /* punt lines with just # (yacc generated) */ + for (cp = bol+1; *cp && (*cp == ' ' || *cp == '\t'); cp++) {} + if (*cp) { p++; goto done; } + } + } + // this is a failure now. we reset the beginning of line. + bol = p+1; + } + } + if (*bol != '#') bol = NULL; +done: + filep->f_p = p; + filep->f_line = lineno; + return bol; +} + +/* + * Strip the file name down to what we want to see in the Makefile. + * It will have objprefix and objsuffix around it. + */ +char *base_name(register char *file) +{ + register char *p; + + file = copy(file); + for(p=file+strlen(file); p>file && *p != '.'; p--) ; + + if (*p == '.') + *p = '\0'; + return(file); +} + +#if defined(USG) && !defined(CRAY) && !defined(SVR4) && !defined(__EMX__) +int rename(char *from, char *to) +{ + (void) unlink (to); + if (link (from, to) == 0) { + unlink (from); + return 0; + } else { + return -1; + } +} +#endif /* USGISH */ + +void redirect(char *line, char *makefile) +{ + struct stat st; + FILE *fdin, *fdout; + char backup[ BUFSIZ ], + buf[ BUFSIZ ]; + bool found = false; + int len; + + /* + * if makefile is "-" then let it pour onto stdout. + */ + if (makefile && *makefile == '-' && *(makefile+1) == '\0') + return; + + /* + * use a default makefile is not specified. + */ + if (!makefile) { + if (stat("Makefile", &st) == 0) + makefile = (char *)"Makefile"; + else if (stat("makefile", &st) == 0) + makefile = (char *)"makefile"; + else + fatalerr("[mM]akefile is not present\n"); + } + else + stat(makefile, &st); + if ((fdin = fopen(makefile, "r")) == NULL) + fatalerr("cannot open \"%s\"\n", makefile); + sprintf(backup, "%s.bak", makefile); + unlink(backup); +#if defined(WIN32) || defined(__EMX__) || defined(__OS2__) + fclose(fdin); +#endif + if (rename(makefile, backup) < 0) + fatalerr("cannot rename %s to %s\n", makefile, backup); +#if defined(WIN32) || defined(__EMX__) || defined(__OS2__) + if ((fdin = fopen(backup, "r")) == NULL) + fatalerr("cannot open \"%s\"\n", backup); +#endif + if ((fdout = freopen(makefile, "w", stdout)) == NULL) + fatalerr("cannot open \"%s\"\n", backup); + len = int(strlen(line)); + while (!found && fgets(buf, BUFSIZ, fdin)) { + if (*buf == '#' && strncmp(line, buf, len) == 0) + found = true; + fputs(buf, fdout); + } + if (!found) { + if (verbose) + warning("Adding new delimiting line \"%s\" and dependencies...\n", + line); + puts(line); /* same as fputs(fdout); but with newline */ + } else if (append) { + while (fgets(buf, BUFSIZ, fdin)) { + fputs(buf, fdout); + } + } + fflush(fdout); +#if defined(USGISH) || defined(_SEQUENT_) || defined(USE_CHMOD) + chmod(makefile, st.st_mode); +#else + fchmod(fileno(fdout), st.st_mode); +#endif /* USGISH */ +} + +#if NeedVarargsPrototypes +void fatalerr(const char *msg, ...) +#else +/*VARARGS*/ +void fatalerr(char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) +#endif +{ +#if NeedVarargsPrototypes + va_list args; +#endif + fprintf(stderr, "%s: error: ", ProgramName); +#if NeedVarargsPrototypes + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); +#else + fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); +#endif + exit (1); +} + +#if NeedVarargsPrototypes +void warning(const char *msg, ...) +#else +/*VARARGS0*/ +void warning(const char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) +#endif +{ +#if NeedVarargsPrototypes + va_list args; +#endif + fprintf(stderr, "%s: warning: ", ProgramName); +#if NeedVarargsPrototypes + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); +#else + fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); +#endif +} + +#if NeedVarargsPrototypes +void warning1(const char *msg, ...) +#else +/*VARARGS0*/ +void warning1(const char *msg,x1,x2,x3,x4,x5,x6,x7,x8,x9) +#endif +{ +#if NeedVarargsPrototypes + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); +#else + fprintf(stderr, msg,x1,x2,x3,x4,x5,x6,x7,x8,x9); +#endif +} + diff --git a/nucleus/tools/dependency_tool/makedepend.man b/nucleus/tools/dependency_tool/makedepend.man new file mode 100644 index 00000000..9c3cdccd --- /dev/null +++ b/nucleus/tools/dependency_tool/makedepend.man @@ -0,0 +1,368 @@ +.\" $XConsortium: mkdepend.man,v 1.15 94/04/17 20:10:37 gildea Exp $ +.\" Copyright (c) 1993, 1994 X Consortium +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining a +.\" copy of this software and associated documentation files (the "Software"), +.\" to deal in the Software without restriction, including without limitation +.\" the rights to use, copy, modify, merge, publish, distribute, sublicense, +.\" and/or sell copies of the Software, and to permit persons to whom the +.\" Software furnished to do so, subject to the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included in +.\" all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +.\" THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +.\" WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +.\" OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +.\" SOFTWARE. +.\" +.\" Except as contained in this notice, the name of the X Consortium shall not +.\" be used in advertising or otherwise to promote the sale, use or other +.\" dealing in this Software without prior written authorization from the +.\" X Consortium. +.TH MAKEDEPEND 1 "Release 6" "X Version 11" +.UC 4 +.SH NAME +makedepend \- create dependencies in makefiles +.SH SYNOPSIS +.B makedepend +[ +.B \-Dname=def +] [ +.B \-Dname +] [ +.B \-Iincludedir +] [ +.B \-Yincludedir +] [ +.B \-a +] [ +.B \-fmakefile +] [ +.B \-oobjsuffix +] [ +.B \-pobjprefix +] [ +.B \-sstring +] [ +.B \-wwidth +] [ +.B \-v +] [ +.B \-m +] [ +\-\^\- +.B otheroptions +\-\^\- +] +sourcefile .\|.\|. +.br +.SH DESCRIPTION +.B Makedepend +reads each +.I sourcefile +in sequence and parses it like a C-preprocessor, +processing all +.I #include, +.I #define, +.I #undef, +.I #ifdef, +.I #ifndef, +.I #endif, +.I #if +and +.I #else +directives so that it can correctly tell which +.I #include, +directives would be used in a compilation. +Any +.I #include, +directives can reference files having other +.I #include +directives, and parsing will occur in these files as well. +.PP +Every file that a +.I sourcefile +includes, +directly or indirectly, +is what +.B makedepend +calls a "dependency". +These dependencies are then written to a +.I makefile +in such a way that +.B make(1) +will know which object files must be recompiled when a dependency has changed. +.PP +By default, +.B makedepend +places its output in the file named +.I makefile +if it exists, otherwise +.I Makefile. +An alternate makefile may be specified with the +.B \-f +option. +It first searches the makefile for +the line +.sp + # DO NOT DELETE THIS LINE \-\^\- make depend depends on it. +.sp +or one provided with the +.B \-s +option, +as a delimiter for the dependency output. +If it finds it, it will delete everything +following this to the end of the makefile +and put the output after this line. +If it doesn't find it, the program +will append the string to the end of the makefile +and place the output following that. +For each +.I sourcefile +appearing on the command line, +.B makedepend +puts lines in the makefile of the form +.sp + sourcefile.o:\0dfile .\|.\|. +.sp +Where "sourcefile.o" is the name from the command +line with its suffix replaced with ".o", +and "dfile" is a dependency discovered in a +.I #include +directive while parsing +.I sourcefile +or one of the files it included. +.SH EXAMPLE +Normally, +.B makedepend +will be used in a makefile target so that typing "make depend" will +bring the dependencies up to date for the makefile. +For example, +.nf + SRCS\0=\0file1.c\0file2.c\0.\|.\|. + CFLAGS\0=\0\-O\0\-DHACK\0\-I\^.\^.\^/foobar\0\-xyz + depend: + makedepend\0\-\^\-\0$(CFLAGS)\0\-\^\-\0$(SRCS) +.fi +.SH OPTIONS +.B Makedepend +will ignore any option that it does not understand so that you may use +the same arguments that you would for +.B cc(1). +.TP 5 +.B \-Dname=def or \-Dname +Define. +This places a definition for +.I name +in +.B makedepend's +symbol table. +Without +.I =def +the symbol becomes defined as "1". +.TP 5 +.B \-Iincludedir +Include directory. +This option tells +.B makedepend +to prepend +.I includedir +to its list of directories to search when it encounters +a +.I #include +directive. +By default, +.B makedepend +only searches the standard include directories (usually /usr/include +and possibly a compiler-dependent directory). +.TP 5 +.B \-Yincludedir +Replace all of the standard include directories with the single specified +include directory; you can omit the +.I includedir +to simply prevent searching the standard include directories. +.TP 5 +.B \-a +Append the dependencies to the end of the file instead of replacing them. +.TP 5 +.B \-fmakefile +Filename. +This allows you to specify an alternate makefile in which +.B makedepend +can place its output. +.TP 5 +.B \-oobjsuffix +Object file suffix. +Some systems may have object files whose suffix is something other +than ".o". +This option allows you to specify another suffix, such as +".b" with +.I -o.b +or ":obj" +with +.I -o:obj +and so forth. +.TP 5 +.B \-pobjprefix +Object file prefix. +The prefix is prepended to the name of the object file. This is +usually used to designate a different directory for the object file. +The default is the empty string. +.TP 5 +.B \-sstring +Starting string delimiter. +This option permits you to specify +a different string for +.B makedepend +to look for in the makefile. +.TP 5 +.B \-wwidth +Line width. +Normally, +.B makedepend +will ensure that every output line that it writes will be no wider than +78 characters for the sake of readability. +This option enables you to change this width. +.TP 5 +.B \-v +Verbose operation. +This option causes +.B makedepend +to emit the list of files included by each input file on standard output. +.TP 5 +.B \-m +Warn about multiple inclusion. +This option causes +.B makedepend +to produce a warning if any input file includes another file more than +once. In previous versions of +.B makedepend +this was the default behavior; the default has been changed to better +match the behavior of the C compiler, which does not consider multiple +inclusion to be an error. This option is provided for backward +compatibility, and to aid in debugging problems related to multiple +inclusion. +.TP 5 +.B "\-\^\- options \-\^\-" +If +.B makedepend +encounters a double hyphen (\-\^\-) in the argument list, +then any unrecognized argument following it +will be silently ignored; a second double hyphen terminates this +special treatment. +In this way, +.B makedepend +can be made to safely ignore esoteric compiler arguments that might +normally be found in a CFLAGS +.B make +macro (see the +.B EXAMPLE +section above). +All options that +.B makedepend +recognizes and appear between the pair of double hyphens +are processed normally. +.SH ALGORITHM +The approach used in this program enables it to run an order of magnitude +faster than any other "dependency generator" I have ever seen. +Central to this performance are two assumptions: +that all files compiled by a single +makefile will be compiled with roughly the same +.I -I +and +.I -D +options; +and that most files in a single directory will include largely the +same files. +.PP +Given these assumptions, +.B makedepend +expects to be called once for each makefile, with +all source files that are maintained by the +makefile appearing on the command line. +It parses each source and include +file exactly once, maintaining an internal symbol table +for each. +Thus, the first file on the command line will take an amount of time +proportional to the amount of time that a normal C preprocessor takes. +But on subsequent files, if it encounter's an include file +that it has already parsed, it does not parse it again. +.PP +For example, +imagine you are compiling two files, +.I file1.c +and +.I file2.c, +they each include the header file +.I header.h, +and the file +.I header.h +in turn includes the files +.I def1.h +and +.I def2.h. +When you run the command +.sp + makedepend\0file1.c\0file2.c +.sp +.B makedepend +will parse +.I file1.c +and consequently, +.I header.h +and then +.I def1.h +and +.I def2.h. +It then decides that the dependencies for this file are +.sp + file1.o:\0header.h\0def1.h\0def2.h +.sp +But when the program parses +.I file2.c +and discovers that it, too, includes +.I header.h, +it does not parse the file, +but simply adds +.I header.h, +.I def1.h +and +.I def2.h +to the list of dependencies for +.I file2.o. +.SH "SEE ALSO" +cc(1), make(1) +.SH BUGS +.B makedepend +parses, but does not currently evaluate, the SVR4 +#predicate(token-list) preprocessor expression; +such expressions are simply assumed to be true. +This may cause the wrong +.I #include +directives to be evaluated. +.PP +Imagine you are parsing two files, +say +.I file1.c +and +.I file2.c, +each includes the file +.I def.h. +The list of files that +.I def.h +includes might truly be different when +.I def.h +is included by +.I file1.c +than when it is included by +.I file2.c. +But once +.B makedepend +arrives at a list of dependencies for a file, +it is cast in concrete. +.SH AUTHOR +Todd Brunhoff, Tektronix, Inc. and MIT Project Athena diff --git a/nucleus/tools/dependency_tool/makedepend.txt b/nucleus/tools/dependency_tool/makedepend.txt new file mode 100644 index 00000000..e8a90f76 --- /dev/null +++ b/nucleus/tools/dependency_tool/makedepend.txt @@ -0,0 +1,264 @@ + + + +User Commands MAKEDEPEND(1) + + + +NNAAMMEE + makedepend - create dependencies in makefiles + +SSYYNNOOPPSSIISS + mmaakkeeddeeppeenndd [ --DDnnaammee==ddeeff ] [ --DDnnaammee ] [ --IIiinncclluuddeeddiirr ] [ + --YYiinncclluuddeeddiirr ] [ --aa ] [ --ffmmaakkeeffiillee ] [ --oooobbjjssuuffffiixx ] [ --ppoobb-- + jjpprreeffiixx ] [ --ssssttrriinngg ] [ --wwwwiiddtthh ] [ --vv ] [ --mm ] [ -- ootthh-- + eerrooppttiioonnss -- ] sourcefile ... + +DDEESSCCRRIIPPTTIIOONN + MMaakkeeddeeppeenndd reads each _s_o_u_r_c_e_f_i_l_e in sequence and parses it + like a C-preprocessor, processing all _#_i_n_c_l_u_d_e_, _#_d_e_f_i_n_e_, + _#_u_n_d_e_f_, _#_i_f_d_e_f_, _#_i_f_n_d_e_f_, _#_e_n_d_i_f_, _#_i_f and _#_e_l_s_e directives so + that it can correctly tell which _#_i_n_c_l_u_d_e_, directives would + be used in a compilation. Any _#_i_n_c_l_u_d_e_, directives can ref- + erence files having other _#_i_n_c_l_u_d_e directives, and parsing + will occur in these files as well. + + Every file that a _s_o_u_r_c_e_f_i_l_e includes, directly or indi- + rectly, is what mmaakkeeddeeppeenndd calls a "dependency". These + dependencies are then written to a _m_a_k_e_f_i_l_e in such a way + that mmaakkee((11)) will know which object files must be recompiled + when a dependency has changed. + + By default, mmaakkeeddeeppeenndd places its output in the file named + _m_a_k_e_f_i_l_e if it exists, otherwise _M_a_k_e_f_i_l_e_. An alternate + makefile may be specified with the --ff option. It first + searches the makefile for the line + + # DO NOT DELETE THIS LINE -- make depend depends on it. + + or one provided with the --ss option, as a delimiter for the + dependency output. If it finds it, it will delete every- + thing following this to the end of the makefile and put the + output after this line. If it doesn't find it, the program + will append the string to the end of the makefile and place + the output following that. For each _s_o_u_r_c_e_f_i_l_e appearing on + the command line, mmaakkeeddeeppeenndd puts lines in the makefile of + the form + + sourcefile.o: dfile ... + + Where "sourcefile.o" is the name from the command line with + its suffix replaced with ".o", and "dfile" is a dependency + discovered in a _#_i_n_c_l_u_d_e directive while parsing _s_o_u_r_c_e_f_i_l_e + or one of the files it included. + +EEXXAAMMPPLLEE + Normally, mmaakkeeddeeppeenndd will be used in a makefile target so + that typing "make depend" will bring the dependencies up to + date for the makefile. For example, + SRCS = file1.c file2.c ... + + + +X Version 11 Last change: Release 6 1 + + + + + + +User Commands MAKEDEPEND(1) + + + + CFLAGS = -O -DHACK -I../foobar -xyz + depend: + makedepend -- $(CFLAGS) -- $(SRCS) + +OOPPTTIIOONNSS + MMaakkeeddeeppeenndd will ignore any option that it does not under- + stand so that you may use the same arguments that you would + for cccc((11)).. + + --DDnnaammee==ddeeff oorr --DDnnaammee + Define. This places a definition for _n_a_m_e in mmaakkeeddee-- + ppeenndd''ss symbol table. Without _=_d_e_f the symbol becomes + defined as "1". + + --IIiinncclluuddeeddiirr + Include directory. This option tells mmaakkeeddeeppeenndd to + prepend _i_n_c_l_u_d_e_d_i_r to its list of directories to search + when it encounters a _#_i_n_c_l_u_d_e directive. By default, + mmaakkeeddeeppeenndd only searches the standard include directo- + ries (usually /usr/include and possibly a compiler- + dependent directory). + + --YYiinncclluuddeeddiirr + Replace all of the standard include directories with + the single specified include directory; you can omit + the _i_n_c_l_u_d_e_d_i_r to simply prevent searching the standard + include directories. + + --aa Append the dependencies to the end of the file instead + of replacing them. + + --ffmmaakkeeffiillee + Filename. This allows you to specify an alternate + makefile in which mmaakkeeddeeppeenndd can place its output. + + --oooobbjjssuuffffiixx + Object file suffix. Some systems may have object files + whose suffix is something other than ".o". This option + allows you to specify another suffix, such as ".b" with + _-_o_._b or ":obj" with _-_o_:_o_b_j and so forth. + + --ppoobbjjpprreeffiixx + Object file prefix. The prefix is prepended to the + name of the object file. This is usually used to desig- + nate a different directory for the object file. The + default is the empty string. + + --ssssttrriinngg + Starting string delimiter. This option permits you to + specify a different string for mmaakkeeddeeppeenndd to look for + in the makefile. + + + + +X Version 11 Last change: Release 6 2 + + + + + + +User Commands MAKEDEPEND(1) + + + + --wwwwiiddtthh + Line width. Normally, mmaakkeeddeeppeenndd will ensure that + every output line that it writes will be no wider than + 78 characters for the sake of readability. This option + enables you to change this width. + + --vv Verbose operation. This option causes mmaakkeeddeeppeenndd to + emit the list of files included by each input file on + standard output. + + --mm Warn about multiple inclusion. This option causes + mmaakkeeddeeppeenndd to produce a warning if any input file + includes another file more than once. In previous ver- + sions of mmaakkeeddeeppeenndd this was the default behavior; the + default has been changed to better match the behavior + of the C compiler, which does not consider multiple + inclusion to be an error. This option is provided for + backward compatibility, and to aid in debugging prob- + lems related to multiple inclusion. + + ---- ooppttiioonnss ---- + If mmaakkeeddeeppeenndd encounters a double hyphen (--) in the + argument list, then any unrecognized argument following + it will be silently ignored; a second double hyphen + terminates this special treatment. In this way, + mmaakkeeddeeppeenndd can be made to safely ignore esoteric com- + piler arguments that might normally be found in a + CFLAGS mmaakkee macro (see the EEXXAAMMPPLLEE section above). All + options that mmaakkeeddeeppeenndd recognizes and appear between + the pair of double hyphens are processed normally. + +AALLGGOORRIITTHHMM + The approach used in this program enables it to run an order + of magnitude faster than any other "dependency generator" I + have ever seen. Central to this performance are two assump- + tions: that all files compiled by a single makefile will be + compiled with roughly the same _-_I and _-_D options; and that + most files in a single directory will include largely the + same files. + + Given these assumptions, mmaakkeeddeeppeenndd expects to be called + once for each makefile, with all source files that are main- + tained by the makefile appearing on the command line. It + parses each source and include file exactly once, maintain- + ing an internal symbol table for each. Thus, the first file + on the command line will take an amount of time proportional + to the amount of time that a normal C preprocessor takes. + But on subsequent files, if it encounter's an include file + that it has already parsed, it does not parse it again. + + For example, imagine you are compiling two files, _f_i_l_e_1_._c + and _f_i_l_e_2_._c_, they each include the header file _h_e_a_d_e_r_._h_, and + + + +X Version 11 Last change: Release 6 3 + + + + + + +User Commands MAKEDEPEND(1) + + + + the file _h_e_a_d_e_r_._h in turn includes the files _d_e_f_1_._h and + _d_e_f_2_._h_. When you run the command + + makedepend file1.c file2.c + + mmaakkeeddeeppeenndd will parse _f_i_l_e_1_._c and consequently, _h_e_a_d_e_r_._h and + then _d_e_f_1_._h and _d_e_f_2_._h_. It then decides that the dependen- + cies for this file are + + file1.o: header.h def1.h def2.h + + But when the program parses _f_i_l_e_2_._c and discovers that it, + too, includes _h_e_a_d_e_r_._h_, it does not parse the file, but sim- + ply adds _h_e_a_d_e_r_._h_, _d_e_f_1_._h and _d_e_f_2_._h to the list of depen- + dencies for _f_i_l_e_2_._o_. + +SSEEEE AALLSSOO + cc(1), make(1) + +BBUUGGSS + mmaakkeeddeeppeenndd parses, but does not currently evaluate, the SVR4 + #predicate(token-list) preprocessor expression; such expres- + sions are simply assumed to be true. This may cause the + wrong _#_i_n_c_l_u_d_e directives to be evaluated. + + Imagine you are parsing two files, say _f_i_l_e_1_._c and _f_i_l_e_2_._c_, + each includes the file _d_e_f_._h_. The list of files that _d_e_f_._h + includes might truly be different when _d_e_f_._h is included by + _f_i_l_e_1_._c than when it is included by _f_i_l_e_2_._c_. But once + mmaakkeeddeeppeenndd arrives at a list of dependencies for a file, it + is cast in concrete. + +AAUUTTHHOORR + Todd Brunhoff, Tektronix, Inc. and MIT Project Athena + + + + + + + + + + + + + + + + + + + + + +X Version 11 Last change: Release 6 4 + + + diff --git a/nucleus/tools/dependency_tool/makefile b/nucleus/tools/dependency_tool/makefile new file mode 100644 index 00000000..14967053 --- /dev/null +++ b/nucleus/tools/dependency_tool/makefile @@ -0,0 +1,17 @@ +CONSOLE_MODE = true + +include cpp/variables.def + +STRICT_WARNINGS = + +PROJECT = dependency_tool +TYPE = application +SOURCE = cppsetup.cpp ifparser.cpp include.cpp parse.cpp pr.cpp +#DEFINITIONS += __BUILD_STATIC_APPLICATION__ +ifeq "$(OP_SYSTEM)" "WIN32" + SOURCE += makedep_version.rc +endif +TARGETS = makedep.exe + +include cpp/rules.def + diff --git a/nucleus/tools/dependency_tool/parse.cpp b/nucleus/tools/dependency_tool/parse.cpp new file mode 100644 index 00000000..6bb46237 --- /dev/null +++ b/nucleus/tools/dependency_tool/parse.cpp @@ -0,0 +1,554 @@ +/* $XConsortium: parse.c,v 1.30 94/04/17 20:10:38 gildea Exp $ */ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "def.h" + +#include + +extern const char *directives[]; +extern inclist maininclist; + +int find_includes(struct filepointer *filep, inclist *file, inclist *file_red, int recursion, bool failOK) +{ + register char *line; + register int type; + bool recfailOK; + + while ((line = getline(filep))) { + switch(type = deftype(line, filep, file_red, file, true)) { + case IF: + doif: + type = find_includes(filep, file, + file_red, recursion+1, failOK); + while ((type == ELIF) || (type == ELIFFALSE) || + (type == ELIFGUESSFALSE)) + type = gobble(filep, file, file_red); + if (type == ELSE) + gobble(filep, file, file_red); + break; + case IFFALSE: + case IFGUESSFALSE: + doiffalse: + if (type == IFGUESSFALSE || type == ELIFGUESSFALSE) + recfailOK = true; + else + recfailOK = failOK; + type = gobble(filep, file, file_red); + if (type == ELSE) + find_includes(filep, file, + file_red, recursion+1, recfailOK); + else + if (type == ELIF) + goto doif; + else + if ((type == ELIFFALSE) || (type == ELIFGUESSFALSE)) + goto doiffalse; + break; + case IFDEF: + case IFNDEF: + if ((type == IFDEF && isdefined(line, file_red, NULL)) + || (type == IFNDEF && !isdefined(line, file_red, NULL))) { + debug(1,(type == IFNDEF ? + "line %d: %s !def'd in %s via %s%s\n" : "", + filep->f_line, line, + file->i_file, file_red->i_file, ": doit")); + type = find_includes(filep, file, + file_red, recursion+1, failOK); + while (type == ELIF || type == ELIFFALSE || type == ELIFGUESSFALSE) + type = gobble(filep, file, file_red); + if (type == ELSE) + gobble(filep, file, file_red); + } + else { + debug(1,(type == IFDEF ? + "line %d: %s !def'd in %s via %s%s\n" : "", + filep->f_line, line, + file->i_file, file_red->i_file, ": gobble")); + type = gobble(filep, file, file_red); + if (type == ELSE) + find_includes(filep, file, + file_red, recursion+1, failOK); + else if (type == ELIF) + goto doif; + else if (type == ELIFFALSE || type == ELIFGUESSFALSE) + goto doiffalse; + } + break; + case ELSE: + case ELIFFALSE: + case ELIFGUESSFALSE: + case ELIF: + if (!recursion) + gobble(filep, file, file_red); + case ENDIF: + if (recursion) + return(type); + case DEFINE: + define(line, file); + break; + case UNDEF: + if (!*line) { + warning("%s, line %d: incomplete undef == \"%s\"\n", + file_red->i_file, filep->f_line, line); + break; + } + undefine(line, file_red); + break; + case INCLUDE: + add_include(filep, file, file_red, line, false, failOK); + break; + case INCLUDEDOT: + add_include(filep, file, file_red, line, true, failOK); + break; + case ERROR: + warning("%s: %d: %s\n", file_red->i_file, + filep->f_line, line); + break; + + case PRAGMA: + case IDENT: + case SCCS: + case EJECT: + case IMPORT: + break; + case -1: + warning("%s", file_red->i_file); + if (file_red != file) + warning1(" (reading %s)", file->i_file); + warning1(", line %d: unknown directive == \"%s\"\n", + filep->f_line, line); + break; + case -2: + warning("%s", file_red->i_file); + if (file_red != file) + warning1(" (reading %s)", file->i_file); + warning1(", line %d: incomplete include == \"%s\"\n", + filep->f_line, line); + break; + } + } + return(-1); +} + +int gobble(register struct filepointer *filep, inclist *file, + inclist *file_red) +{ + register char *line; + register int type; + + while ((line = getline(filep))) { + switch(type = deftype(line, filep, file_red, file, false)) { + case IF: + case IFFALSE: + case IFGUESSFALSE: + case IFDEF: + case IFNDEF: + type = gobble(filep, file, file_red); + while ((type == ELIF) || (type == ELIFFALSE) || + (type == ELIFGUESSFALSE)) + type = gobble(filep, file, file_red); + if (type == ELSE) + (void)gobble(filep, file, file_red); + break; + case ELSE: + case ENDIF: + debug(0,("%s, line %d: #%s\n", + file->i_file, filep->f_line, + directives[type])); + return(type); + case DEFINE: + case UNDEF: + case INCLUDE: + case INCLUDEDOT: + case PRAGMA: + case ERROR: + case IDENT: + case SCCS: + case EJECT: + case IMPORT: + break; + case ELIF: + case ELIFFALSE: + case ELIFGUESSFALSE: + return(type); + case -1: + warning("%s, line %d: unknown directive == \"%s\"\n", + file_red->i_file, filep->f_line, line); + break; + } + } + return(-1); +} + +/* + * Decide what type of # directive this line is. + */ +int deftype(register char *line, register struct filepointer *filep, + register inclist *file_red, register inclist *file, + int parse_it) +{ + register char *p; + char *directive, savechar; + register int ret; + + /* + * Parse the directive... + */ + directive=line+1; + while (*directive == ' ' || *directive == '\t') + directive++; + + p = directive; + while (*p >= 'a' && *p <= 'z') + p++; + savechar = *p; + *p = '\0'; + ret = match(directive, directives); + *p = savechar; + + /* If we don't recognize this compiler directive or we happen to just + * be gobbling up text while waiting for an #endif or #elif or #else + * in the case of an #elif we must check the zero_value and return an + * ELIF or an ELIFFALSE. + */ + + if (ret == ELIF && !parse_it) + { + while (*p == ' ' || *p == '\t') + p++; + /* + * parse an expression. + */ + debug(0,("%s, line %d: #elif %s ", + file->i_file, filep->f_line, p)); + ret = zero_value(p, filep, file_red); + if (ret != IF) + { + debug(0,("false...\n")); + if (ret == IFFALSE) + return(ELIFFALSE); + else + return(ELIFGUESSFALSE); + } + else + { + debug(0,("true...\n")); + return(ELIF); + } + } + + if (ret < 0 || ! parse_it) + return(ret); + + /* + * now decide how to parse the directive, and do it. + */ + while (*p == ' ' || *p == '\t') + p++; + switch (ret) { + case IF: + /* + * parse an expression. + */ + ret = zero_value(p, filep, file_red); + debug(0,("%s, line %d: %s #if %s\n", + file->i_file, filep->f_line, ret?"false":"true", p)); + break; + case IFDEF: + case IFNDEF: + debug(0,("%s, line %d: #%s %s\n", + file->i_file, filep->f_line, directives[ret], p)); + case UNDEF: + /* + * separate the name of a single symbol. + */ + while (isalnum(*p) || *p == '_') + *line++ = *p++; + *line = '\0'; + break; + case INCLUDE: + debug(2,("%s, line %d: #include %s\n", + file->i_file, filep->f_line, p)); + + /* Support ANSI macro substitution */ + { + struct symtab *sym = isdefined(p, file_red, NULL); + while (sym) { + p = sym->s_value; + debug(3,("%s : #includes SYMBOL %s = %s\n", + file->i_incstring, + sym -> s_name, + sym -> s_value)); + /* mark file as having included a 'soft include' */ + file->i_included_sym = true; + sym = isdefined(p, file_red, NULL); + } + } + + /* + * Separate the name of the include file. + */ + while (*p && *p != '"' && *p != '<') + p++; + if (! *p) + return(-2); + if (*p++ == '"') { + ret = INCLUDEDOT; + while (*p && *p != '"') + *line++ = *p++; + } else + while (*p && *p != '>') + *line++ = *p++; + *line = '\0'; + break; + case DEFINE: + /* + * copy the definition back to the beginning of the line. + */ + strcpy (line, p); + break; + case ELSE: + case ENDIF: + case ELIF: + case PRAGMA: + case ERROR: + case IDENT: + case SCCS: + case EJECT: + case IMPORT: + debug(0,("%s, line %d: #%s\n", + file->i_file, filep->f_line, directives[ret])); + /* + * nothing to do. + */ + break; + } + return(ret); +} + +symtab *isdefined(register char *symbol, inclist *file, + inclist **srcfile) +{ + register struct symtab *val; + + if ((val = slookup(symbol, &maininclist))) { + debug(1,("%s defined on command line\n", symbol)); + if (srcfile != NULL) *srcfile = &maininclist; + return(val); + } + if ((val = fdefined(symbol, file, srcfile))) + return(val); + debug(1,("%s not defined in %s\n", symbol, file->i_file)); + return(NULL); +} + +struct symtab *fdefined(register char *symbol, inclist *file, inclist **srcfile) +{ + register inclist **ip; + register struct symtab *val; + register int i; + static int recurse_lvl = 0; + + if (file->i_defchecked) + return(NULL); + file->i_defchecked = true; + if ((val = slookup(symbol, file))) + debug(1,("%s defined in %s as %s\n", symbol, file->i_file, val->s_value)); + if (val == NULL && file->i_list) + { + for (ip = file->i_list, i=0; i < file->i_listlen; i++, ip++) + if ((val = fdefined(symbol, *ip, srcfile))) { + break; + } + } + else if (val != NULL && srcfile != NULL) *srcfile = file; + recurse_lvl--; + file->i_defchecked = false; + + return(val); +} + +/* + * Return type based on if the #if expression evaluates to 0 + */ +int zero_value(register char *exp, register struct filepointer *filep, + register inclist *file_red) +{ + if (cppsetup(exp, filep, file_red)) + return(IFFALSE); + else + return(IF); +} + +void define(char *def, inclist *file) +{ + char *val; + + /* Separate symbol name and its value */ + val = def; + while (isalnum(*val) || *val == '_') + val++; + if (*val) + *val++ = '\0'; + while (*val == ' ' || *val == '\t') + val++; + + if (!*val) + val = (char *)"1"; + define2(def, val, file); +} + +void define2(char *name, char *val, inclist *file) +{ + int first, last, below; + register struct symtab *sp = NULL, *dest; + + /* Make space if it's needed */ + if (file->i_defs == NULL) + { + file->i_defs = (struct symtab *) + malloc(sizeof (struct symtab) * SYMTABINC); + file->i_deflen = SYMTABINC; + file->i_ndefs = 0; + } + else if (file->i_ndefs == file->i_deflen) + file->i_defs = (struct symtab *) + realloc(file->i_defs, + sizeof(struct symtab)*(file->i_deflen+=SYMTABINC)); + + if (file->i_defs == NULL) + fatalerr("malloc()/realloc() failure in insert_defn()\n"); + + below = first = 0; + last = file->i_ndefs - 1; + while (last >= first) + { + /* Fast inline binary search */ + register char *s1; + register char *s2; + register int middle = (first + last) / 2; + + /* Fast inline strchr() */ + s1 = name; + s2 = file->i_defs[middle].s_name; + while (*s1++ == *s2++) + if (s2[-1] == '\0') break; + + /* If exact match, set sp and break */ + if (*--s1 == *--s2) + { + sp = file->i_defs + middle; + break; + } + + /* If name > i_defs[middle] ... */ + if (*s1 > *s2) + { + below = first; + first = middle + 1; + } + /* else ... */ + else + { + below = last = middle - 1; + } + } + + /* Search is done. If we found an exact match to the symbol name, + just replace its s_value */ + if (sp != NULL) + { + free(sp->s_value); + sp->s_value = copy(val); + return; + } + + sp = file->i_defs + file->i_ndefs++; + dest = file->i_defs + below + 1; + while (sp > dest) + { + *sp = sp[-1]; + sp--; + } + sp->s_name = copy(name); + sp->s_value = copy(val); +} + +struct symtab *slookup(register char *symbol, register inclist *file) +{ + register int first = 0; + register int last = file->i_ndefs - 1; + + if (file) while (last >= first) + { + /* Fast inline binary search */ + register char *s1; + register char *s2; + register int middle = (first + last) / 2; + + /* Fast inline strchr() */ + s1 = symbol; + s2 = file->i_defs[middle].s_name; + while (*s1++ == *s2++) + if (s2[-1] == '\0') break; + + /* If exact match, we're done */ + if (*--s1 == *--s2) + { + return file->i_defs + middle; + } + + /* If symbol > i_defs[middle] ... */ + if (*s1 > *s2) + { + first = middle + 1; + } + /* else ... */ + else + { + last = middle - 1; + } + } + return(NULL); +} + +void undefine(char *symbol, register inclist *file) +{ + register struct symtab *ptr; + inclist *srcfile; + while ((ptr = isdefined(symbol, file, &srcfile)) != NULL) + { + srcfile->i_ndefs--; + for (; ptr < srcfile->i_defs + srcfile->i_ndefs; ptr++) + *ptr = ptr[1]; + } +} diff --git a/nucleus/tools/dependency_tool/pr.cpp b/nucleus/tools/dependency_tool/pr.cpp new file mode 100644 index 00000000..e1bdf7b3 --- /dev/null +++ b/nucleus/tools/dependency_tool/pr.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 1993, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + +*/ + +#ifdef __WIN32__ + #pragma warning(disable : 4996) +#endif + +#include "def.h" + +#include + +extern struct inclist inc_list[ MAXFILES ], *inclistp; +extern char *objprefix; +extern char *objsuffix; +extern int width; +extern bool printed; +extern bool verbose; +extern bool show_where_not; + +void add_include(filepointer *filep, inclist *file, + inclist *file_red, char *include, bool dot, bool failOK) +{ + register struct inclist *newfile; + register struct filepointer *content; + + /* + * First decide what the pathname of this include file really is. + */ + newfile = inc_path(file->i_file, include, dot, failOK); + if (newfile == NULL) { + if (failOK) + return; + if (file != file_red) + warning("%s (reading %s, line %d): ", + file_red->i_file, file->i_file, filep->f_line); + else + warning("%s, line %d: ", file->i_file, filep->f_line); + warning1("cannot find include file \"%s\"\n", include); +fatalerr("cannot find include file \"%s\"\n", include); + show_where_not = true; + newfile = inc_path(file->i_file, include, dot, failOK); + show_where_not = false; + } + + if (newfile) { + included_by(file, newfile); + if (!newfile->i_searched) { + newfile->i_searched = true; + content = getfile(newfile->i_file); + find_includes(content, newfile, file_red, 0, failOK); + freefile(content); + } + } +} + +void recursive_pr_include(register struct inclist *head, register char *file, + register char *base) +{ + register int i; + + if (head->i_marked) + return; + head->i_marked = true; + if (head->i_file != file) { + bool rc_file = false; + if ((strlen(file) >= 3) && !strcmp(file + strlen(file) - 3, ".rc")) + rc_file = true; + pr(head, file, base, rc_file); + } + for (i=0; ii_listlen; i++) + recursive_pr_include(head->i_list[ i ], file, base); +} + +void pr(register struct inclist *ip, char *file, char *base, bool rc_file) +{ + static char *lastfile; + static int current_len; + register int len, i; + char buf[ BUFSIZ ]; + + printed = true; + len = int(strlen(ip->i_file)+1); + if (current_len + len > width || file != lastfile) { + lastfile = file; + char *temp_suff = objsuffix; + if (rc_file) temp_suff = (char *)".res"; + sprintf(buf, "\n%s%s%s: %s ", objprefix, base, temp_suff, ip->i_file); + len = current_len = int(strlen(buf)); + } + else { + strcpy(buf, ip->i_file); +//printf("ip->file=%s\n", ip->i_file); + char tmp[2] = { ' ', '\0' }; +//printf("tmp=%s\n", tmp); + strcat(buf, tmp); +//printf("buf=%s\n", buf); + current_len += len; + } + fwrite(buf, len, 1, stdout); + + // If verbose is set, then print out what this file includes. + if (! verbose || ip->i_list == NULL || ip->i_notified) + return; + ip->i_notified = true; + lastfile = NULL; + printf("\n# %s includes:", ip->i_file); + for (i=0; ii_listlen; i++) + printf("\n#\t%s", ip->i_list[ i ]->i_incstring); +} + diff --git a/nucleus/tools/dependency_tool/readme.txt b/nucleus/tools/dependency_tool/readme.txt new file mode 100644 index 00000000..e9f660e2 --- /dev/null +++ b/nucleus/tools/dependency_tool/readme.txt @@ -0,0 +1,55 @@ + +this port of makedepend is now called makedep, since it's a shorter name, +and therefore better...? or at least shorter. +the code has been ported to visual c++ 5.x-7.x as well as remaining compatible +with unix and linux. it has been made to comply with c++ prototype rules. +also, support for excluding directories from dependency checking has been +added (with a -X flag that takes the directory name). + +Chris Koeritz +fred@gruntose.com +(original 3/4/1999) +(updated 9/7/2000) +(updated 3/18/2004) + +============================================================================ + +makedepend +---------- + +This is a quick and rude port of the X11 R6 makedepend to OS/2 using icc. + +I have taken the code from FreeBSD 2.0.5, which I happened to have handy. + +I have added a feature I wanted: the switch -iENVIRONMENTVARIABLE will add +all semicolon delimited directories in ENVIRONMENTVARIABLE to the list of +include directories. + +One obvious use is: makedepend -i INCLUDE a.c + +If you do not want the system header files in you dependencies, you might use: + +set MYINCLUDE=\mytree\include;\mytree\subproj\include + +makedepend -i MYINCLUDE a.c + +but + +makedepend -I \mytree\include -I \mytree\subproj\include a.c + +which is the 'normal' way of doing things, is also possible. + +Sources +------- + +Todd Brunhoff wrote this program. Thanks. + +To build makedepend, I use icc and GNU make. nmake will barf over the makefile. + +And yes, I use long filenames. If you don't like that, edit the makefile. + +I have compiled these sources under NT with VC++ 4.2 without any hassles (different +makefile, though). + +Lars Immisch +lars@ibp.de diff --git a/nucleus/tools/dependency_tool/version.ini b/nucleus/tools/dependency_tool/version.ini new file mode 100644 index 00000000..8444aa59 --- /dev/null +++ b/nucleus/tools/dependency_tool/version.ini @@ -0,0 +1,5 @@ +[version] +description=Makefile Dependency Generator +root=makedep +name=Dependency_Maker +extension=exe diff --git a/nucleus/tools/makefile b/nucleus/tools/makefile new file mode 100644 index 00000000..f3781eeb --- /dev/null +++ b/nucleus/tools/makefile @@ -0,0 +1,7 @@ +include variables.def + +PROJECT = tools_hierarchy +BUILD_BEFORE = clam_tools dependency_tool simple_utilities solution_solvers + +include rules.def + diff --git a/nucleus/tools/simple_utilities/create_guid.cpp b/nucleus/tools/simple_utilities/create_guid.cpp new file mode 100644 index 00000000..104bde72 --- /dev/null +++ b/nucleus/tools/simple_utilities/create_guid.cpp @@ -0,0 +1,138 @@ +/*****************************************************************************\ +* * +* Name : create_guid * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* This program generates a globally unique identifier using the operating * +* system's support. The resulting id can be used to tag items that must be * +* uniquely named. * +* * +******************************************************************************* +* Copyright (c) 2006-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif + +using namespace application; +using namespace basis; +using namespace loggers; +using namespace mathematics; +using namespace structures; +using namespace textual; + +#define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT) + +// this is an example GUID in the DCE format: +// +// {12345678-1234-1234-1234-123456789012} +// +// each position can be a hexadecimal digit, ranging from 0 to F. +// the full size is measured as 32 nibbles or 16 bytes or 128 bits. + +class create_guid : public application_shell +{ +public: + create_guid() : application_shell() {} + DEFINE_CLASS_NAME("create_guid"); + int execute(); +}; + +int create_guid::execute() +{ +// FUNCDEF("execute"); + SETUP_CONSOLE_LOGGER; +#ifdef __UNIX__ + +// this is completely bogus for the time being. it just produces a random +// number rather than a guid. + #define add_random \ + faux_guid += astring(string_manipulation::hex_to_char \ + (randomizer().inclusive(0, 0xf)), 1) + + astring faux_guid("{"); + for (int i = 0; i < 8; i++) add_random; + faux_guid += "-"; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 4; i++) add_random; + faux_guid += "-"; + } + for (int i = 0; i < 8; i++) add_random; + faux_guid += "}"; + BASE_LOG(faux_guid.lower()); +#elif defined (__WIN32__) + GUID guid; + CoCreateGuid(&guid); + const int BUFFER_SIZE = 1024; + LPOLESTR wide_buffer = new WCHAR[BUFFER_SIZE + 4]; + StringFromGUID2(guid, wide_buffer, BUFFER_SIZE); + const int BYTE_BUFFER_SIZE = BUFFER_SIZE * 2 + 4; + char buffer[BYTE_BUFFER_SIZE]; + WideCharToMultiByte(CP_UTF8, 0, wide_buffer, -1, buffer, BYTE_BUFFER_SIZE, + NULL, NULL); + astring guid_text = buffer; + delete [] wide_buffer; + BASE_LOG(guid_text); +#else + #error unknown operating system; no support for guids. +#endif + + return 0; +} + +HOOPLE_MAIN(create_guid, ) + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/simple_utilities/makefile b/nucleus/tools/simple_utilities/makefile new file mode 100644 index 00000000..ee683578 --- /dev/null +++ b/nucleus/tools/simple_utilities/makefile @@ -0,0 +1,27 @@ +CONSOLE_MODE = t + +include cpp/variables.def + +PROJECT = simplistic_utils +TYPE = application +TARGETS = create_guid.exe playsound.exe short_path.exe sleep_ms.exe \ + zap_process.exe +ifeq "$(OMIT_VERSIONS)" "" + SOURCE += simple_utils_version.rc +endif +DEFINITIONS += __BUILD_STATIC_APPLICATION__ +UNDEFINITIONS += ENABLE_MEMORY_HOOK ENABLE_CALLSTACK_TRACKING +ifeq "$(OP_SYSTEM)" "WIN32" + # static C runtime support... +#hmmm: resurrect this as a particular build type, so makefiles don't need to know this... + COMPILER_FLAGS += -MT + LIBS_USED += netapi32.lib + ifeq "$(DEBUG)" "" + LIBS_USED += libcmt.lib + else + LIBS_USED += libcmtd.lib + endif +endif + +include cpp/rules.def + diff --git a/nucleus/tools/simple_utilities/playsound.cpp b/nucleus/tools/simple_utilities/playsound.cpp new file mode 100644 index 00000000..c1ddf4b2 --- /dev/null +++ b/nucleus/tools/simple_utilities/playsound.cpp @@ -0,0 +1,73 @@ +/*****************************************************************************\ +* * +* Name : playsound * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* A program intended to be as simple as possible and to play only WAV * +* files on the win32 platform. It was decided that this was needed because * +* windows media player suddenly became political and started complaining * +* when other programs were registered to play different sound types. * +* All this does is play WAV files. That's it. * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include +#endif + +using namespace basis; +using namespace loggers; +using namespace structures; + +///HOOPLE_STARTUP_CODE; +//hmmm: this needs to arise from obscurity too? + +int main(int argc, char *argv[]) +{ + console_logger out; + if (argc < 2) { + out.log(astring(argv[0]) + " usage:", ALWAYS_PRINT); + out.log(astring("This program takes one or more parameters which are interpreted"), ALWAYS_PRINT); + out.log(astring("as the names of sound files."), ALWAYS_PRINT); + return 12; + } + for (int i = 1; i < argc; i++) { +// out.log(astring(astring::SPRINTF, "soundfile %d: %s", i, argv[i])); +#ifdef __WIN32__ + if (!PlaySound(to_unicode_temp(argv[i]), NIL, SND_FILENAME)) + out.log(astring("failed to play ") + argv[i], ALWAYS_PRINT); +#else + out.log(astring("this program is a NO-OP, ignoring ") + argv[i], ALWAYS_PRINT); +#endif + } + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/simple_utilities/short_path.cpp b/nucleus/tools/simple_utilities/short_path.cpp new file mode 100644 index 00000000..b9582492 --- /dev/null +++ b/nucleus/tools/simple_utilities/short_path.cpp @@ -0,0 +1,58 @@ +/*****************************************************************************\ +* * +* Name : short_path * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* This program converts a pathname to its 8.3 name. Only for windows. * +* * +******************************************************************************* +* Copyright (c) 2007-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include + +#include +#include +#ifdef __WIN32__ + #include +#endif + +using namespace basis; +using namespace structures; + +///HOOPLE_STARTUP_CODE; + +int main(int argc, char *argv[]) +{ + astring shorty('\0', 2048); + if (argc < 2) { + printf("This program needs a path to convert to its short form.\n"); + return 23; + } +#ifdef __WIN32__ + GetShortPathNameA(argv[1], shorty.s(), 2045); +#else + strcpy(shorty.s(), argv[1]); +#endif + shorty.replace_all('\\', '/'); + printf("%s", shorty.s()); + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/simple_utilities/sleep_ms.cpp b/nucleus/tools/simple_utilities/sleep_ms.cpp new file mode 100644 index 00000000..3b3d87ed --- /dev/null +++ b/nucleus/tools/simple_utilities/sleep_ms.cpp @@ -0,0 +1,77 @@ +/*****************************************************************************\ +* * +* Name : sleep_ms * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Takes a number from the command line and sleeps for that many milli- * +* seconds. * +* * +******************************************************************************* +* Copyright (c) 2001-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include + +#include + +using namespace application; +using namespace basis; +using namespace timely; + +DEFINE_ARGC_AND_ARGV; +///DEFINE_INSTANCE_HANDLE; + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + printf("%s usage:\nThe first parameter is taken as the number of " + "milliseconds to sleep.\n", argv[0]); + return 1; + } + + int snooze_ms; + sscanf(argv[1], "%d", &snooze_ms); + time_control::sleep_ms(snooze_ms); + return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/simple_utilities/version.ini b/nucleus/tools/simple_utilities/version.ini new file mode 100644 index 00000000..c1e1b1d8 --- /dev/null +++ b/nucleus/tools/simple_utilities/version.ini @@ -0,0 +1,6 @@ +[version] +description = Simple Utility Collection +root = simple_utils +name = Simple Utilities +extension = exe + diff --git a/nucleus/tools/simple_utilities/zap_process.cpp b/nucleus/tools/simple_utilities/zap_process.cpp new file mode 100644 index 00000000..70473765 --- /dev/null +++ b/nucleus/tools/simple_utilities/zap_process.cpp @@ -0,0 +1,123 @@ +/*****************************************************************************\ +* * +* Name : zap_process * +* Author : Chris Koeritz * +* * +* Purpose: * +* * +* Whacks a process named on the command line, if possible. * +* * +******************************************************************************* +* Copyright (c) 2000-$now By Author. 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 2 of * +* the License or (at your option) any later version. This is online at: * +* http://www.fsf.org/copyleft/gpl.html * +* Please send any updates to: fred@gruntose.com * +\*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//HOOPLE_STARTUP_CODE; +//hmmm: missing + +using namespace application; +using namespace basis; +using namespace filesystem; +using namespace loggers; +using namespace processes; +using namespace structures; + +int main(int argc, char *argv[]) +{ + console_logger out; + command_line cmds(argc, argv); + if ( (cmds.entries() < 1) + || (cmds.get(0).type() != command_parameter::VALUE) ) { + out.log(cmds.program_name().basename().raw() + " usage:\n" + "this takes a single parameter, which is the name of a program\n" + "to hunt down and eradicate. this will zap the program without\n" + "any warning or any chance for it to save its state.", ALWAYS_PRINT); + return 1; + } + astring program_name = cmds.get(0).text(); + program_name.to_lower(); + process_entry_array processes; + process_control proc_con; + if (!proc_con.query_processes(processes)) + non_continuable_error("procto", "main", "failed to query processes!"); + bool found = false; + bool success = true; + for (int i = 0; i < processes.length(); i++) { + filename path = processes[i].path(); +// out.log(astring("got process path: ") + path.raw(), ALWAYS_PRINT); + astring base = path.basename().raw().lower(); + if (base == program_name) { + found = true; +// out.log("would whack this entry:"); +// out.log(processes[i].text_form()); + bool ret = proc_con.zap_process(processes[i]._process_id); + if (ret) + out.log(a_sprintf("Killed process %d [", processes[i]._process_id) + + program_name + astring("]"), ALWAYS_PRINT); + else { + out.log(astring(astring::SPRINTF, "Failed to zap process %d [", + processes[i]._process_id) + program_name + astring("]"), ALWAYS_PRINT); + success = false; + } + } + } + if (!found) + out.log(astring("Could not find the program named ") + program_name, ALWAYS_PRINT); + if (!success) return 123; + else return 0; +} + +#ifdef __BUILD_STATIC_APPLICATION__ + // static dependencies found by buildor_gen_deps.sh: + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/tools/solution_solvers/check_resource_ids.sh b/nucleus/tools/solution_solvers/check_resource_ids.sh new file mode 100644 index 00000000..d4b9dab9 --- /dev/null +++ b/nucleus/tools/solution_solvers/check_resource_ids.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# finds all the resource ids in resource headers (only those named the +# canonical resource.h name) and discovers any duplicates. the duplicates +# are shown with their symbolic names and file locations. + +TEMP_RESOURCE_HEADERS=/tmp/resrc_headers.txt + +sp='[ ]' # space and tab. + +# find all the resource headers so we can look at their contents. +find "$BUILD_TOP" -type f -iname "resource.h" | \ + grep -vi 3rdparty | \ + grep -vi admin_items | \ + grep -v app >"$TEMP_RESOURCE_HEADERS" +#hmmm: above ignores *anything* with app in the name. +# grep -v app_src >"$TEMP_RESOURCE_HEADERS" + +FULLDEFS=/tmp/full_definition_list.txt +# clean up prior versions. +rm -f "$FULLDEFS" + +# iterate through all the resource headers we found. +while read line; do +#echo "file=$line" + # find any lines that define a resource id. remove any that are part of + # visual studio's tracking system for next id to assign (_APS_NEXT crud). + chop_line="$(echo $line | sed -e 's/[\\\/]/+/g')" + grep "^$sp*#define$sp*[_A-Za-z0-9][_A-Za-z0-9]*$sp*[0-9][0-9]*$sp*$" <"$line" | \ + grep -v "_APS_NEXT" | \ + grep -v "_APS_3D" | \ + grep -v "$sp*\/\/.*" | \ + sed -e "s/^$sp*#define$sp*\([_A-Za-z0-9][_A-Za-z0-9]*\)$sp*\([0-9][0-9]*\)$sp*$/\1=\2#$chop_line/" >>"$FULLDEFS" +done <"$TEMP_RESOURCE_HEADERS" + +# our accumulated lists of names and ids (in order per list). +declare -a resource_names=() +declare -a resource_ids=() +declare -a resource_files=() + +# iterate through the definitions list and compile the set of known ids. +while read line; do + name=$(echo $line | sed -e 's/\([^=]*\)=[^#]*#.*/\1/') + id=$(echo $line | sed -e 's/[^=]*=\([^#]*\)#.*/\1/') + file=$(echo $line | sed -e 's/[^=]*=[^#]*#\(.*\)/\1/') + +#echo got name $name +#echo got id $id +#echo got file $file + next_index=${#resource_names[*]} +#echo next ind is $next_index + resource_names[${next_index}]=$name + resource_ids[${next_index}]=$id + resource_files[${next_index}]=$id + +done <"$FULLDEFS" + +echo done reading all definitions. + +JUST_IDS=/tmp/ids_list.txt +rm -f "$JUST_IDS" + +i=0 +while [[ i -le $next_index ]]; do + echo ${resource_ids[$i]} >>"$JUST_IDS" + ((i++)) +done + +echo done accumulating list of integer ids. + +id_size=$(wc "$JUST_IDS") + +JUST_IDS_TEMP=/tmp/ids_list_temp.txt + +sort "$JUST_IDS" | uniq >"$JUST_IDS_TEMP" +id_temp_size=$(wc "$JUST_IDS_TEMP") +if [ "$id_size" == "$id_temp_size" ]; then + echo "Your IDs are all unique! Ending analysis." + exit 0 +fi + +echo "Your ids are *NOT* all unique; the repeated ones are:" +sort "$JUST_IDS" | uniq -d >"$JUST_IDS_TEMP" + +while read line; do + id="$line" + echo "=== identifier $id ===" + grep "=$line#" "$FULLDEFS" | sed -e 's/\+/\//g' | sed -e 's/#/\ +/' +done <"$JUST_IDS_TEMP" + + diff --git a/nucleus/tools/solution_solvers/clean_vcxproj.sh b/nucleus/tools/solution_solvers/clean_vcxproj.sh new file mode 100644 index 00000000..8f74ba4a --- /dev/null +++ b/nucleus/tools/solution_solvers/clean_vcxproj.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# updates a vcxproj that had been converted from prior visual studio 2005 +# to the newer 2010. + +parms=($*) + +outdir="$HOME/fixed_proj" + +if [ ! -d "$outdir" ]; then mkdir -p $outdir; fi + +for i in ${parms[*]}; do + curr_parm="$i" + base=$(basename "$curr_parm") + echo fixing $base + + cat "$curr_parm" | + sed -e 's/v80<\/PlatformToolset>/v100<\/PlatformToolset>/g' | + sed -e 's/ide_files/build/g' | + sed -e 's/release_[de][lx][el]/release/g' | + sed -e 's/debug_[de][lx][el]/debug/g' | + sed -e 's///g' | + sed -e 's///g' | + sed -e 's///g' | + sed -e 's/v2.0<\/TargetFrameworkVersion>/v4.0<\/TargetFrameworkVersion>/g' | + sed -e 's/\.\.\\\.\.\\\.\.\\build/\.\.\\\.\.\\\.\.\\\.\.\\\.\.\\build/g' | + sed -e 's/\.\.\\\.\.\\lib_src/\.\.\\\.\.\\\.\.\\\.\.\\\.\.\\libraries/g' | + cat >"$outdir/$base" + +done + diff --git a/nucleus/tools/solution_solvers/extract_projects.sh b/nucleus/tools/solution_solvers/extract_projects.sh new file mode 100644 index 00000000..c36525c0 --- /dev/null +++ b/nucleus/tools/solution_solvers/extract_projects.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# this is a simple script that finds the project files listed in a solution file. + +solution_name="$1"; shift +if [ -z "$solution_name" ]; then + echo This script needs a solution or project file name. It will locate all the + echo projects listed in that file. + exit 3 +fi + +grep -i proj "$solution_name" | sed -n -e 's/.*"\([^"]*proj\)".*/\1/p' | sed -e 's/.*[\\\/]\([^\\\/]*\)/\1/' | tr A-Z a-z | sort | uniq + diff --git a/nucleus/tools/solution_solvers/find_multilisted_projects_in_solutions.sh b/nucleus/tools/solution_solvers/find_multilisted_projects_in_solutions.sh new file mode 100644 index 00000000..75ee4a31 --- /dev/null +++ b/nucleus/tools/solution_solvers/find_multilisted_projects_in_solutions.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# this script checks all the known solution files to ensure that no project file is +# listed more than once in the whole set. + +export errors_seen=0 + +# where we'll send our generated files. +export OUT_DIR="$TMP" + +export CORE_PROJ="$OUT_DIR/core_projects.txt" +export SHARED_PROJ="$OUT_DIR/shared_projects.txt" +export MIDDLEWARE_PROJ="$OUT_DIR/middleware_projects.txt" + +#hmmm: fix references! +# extract all the project names from the solution files. +bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/libraries/solutions/lightlink_core.sln" >"$CORE_PROJ" +bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/libraries/solutions/lightlink_shared.sln" >"$SHARED_PROJ" +bash $BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh "$BUILD_TOP/products/Middleware/middleware.sln" >"$MIDDLEWARE_PROJ" + +export TEMP_COMPARE_FILE="$OUT_DIR/commonlines.txt" + +# outer loop is all but the most dependent project. +for i in "$CORE_PROJ" "$SHARED_PROJ"; do + # inner loop is all but the most depended on project. + for j in "$SHARED_PROJ" "$MIDDLEWARE_PROJ"; do + if [ "$i" == "$j" ]; then continue; fi +#echo comparing $i and $j + comm -1 -2 "$i" "$j" >"$TEMP_COMPARE_FILE" + if [ -s "$TEMP_COMPARE_FILE" ]; then + echo "ERROR: the two files $(basename $i) and $(basename $j) share common projects." + ((errors_seen++)) + fi + done +done + +if [ $errors_seen -gt 0 ]; then + echo "ERROR: There were errors detected during the checks!" + exit 3 +else + echo "No problems were seen in any checks." +fi + diff --git a/nucleus/tools/solution_solvers/find_output_pathers.sh b/nucleus/tools/solution_solvers/find_output_pathers.sh new file mode 100644 index 00000000..204fd327 --- /dev/null +++ b/nucleus/tools/solution_solvers/find_output_pathers.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# this script locates any project files that mention the output path setting (OutputPath). +# these are extra suspicious due to the problems caused for our solutions when files have the +# OutputPath specified rather than allowing the parent's settings to be inherited. + +# only works when repo dir is at the top of the full builds area. +# we need like a top dir of some sort. +find $REPOSITORY_DIR -iname "*proj" -exec grep -l OutputPath {} ';' >~/outputpath_mentioners.txt diff --git a/nucleus/tools/solution_solvers/verify_project_file.sh b/nucleus/tools/solution_solvers/verify_project_file.sh new file mode 100644 index 00000000..841bdf4d --- /dev/null +++ b/nucleus/tools/solution_solvers/verify_project_file.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# this script locates the solution that a project file belongs in and decides whether the +# project file contains any bad references to projects that are outside of its solution. + +proj_file="$1"; shift + +if [ -z "$proj_file" ]; then + echo This script needs one parameter that is a project file to be verified. + echo The file will be located in a solution file, and then checked for all project + echo references being in the same solution file. + exit 3 +fi + +############## + +# look for the solution that a project belongs to. +function find_solution_membership() +{ + local proj="$1"; shift + found_solution= + for i in ${SOLUTIONS[*]}; do + # secret sauce--don't match on any old reference to the file; we need + # it to be coming from the real project definition. + grep -i "$proj\"," "$i" &>/dev/null + if [ $? -eq 0 ]; then +#echo "$proj found in solution $i" + found_solution="$i" + break + fi + done +} + +function complain_about_project() +{ + filename="$1"; shift + echo "!!" + echo "Project $proj_base is in error (at $proj_file)" + echo "it references project $filename which is external to the solution." + echo "!!" + ((errors_seen++)) +} + +############## + +proj_base="$(basename $proj_file)" + +CHECKERS="$TMP/checking_refs.txt" + +errors_seen=0 + +#hmmm fix this for big time +export SOLUTIONS=("$BUILD_TOP/libraries/solutions/"*.sln "$BUILD_TOP/products/"*/*.sln) + +find_solution_membership "$proj_base" + +#echo found sol is $found_solution + +if [ -z "$found_solution" ]; then + echo error: could not find the solution containing $proj_base + exit 3 +fi + +# get all the project references from the project file being tested. +#hmmm: fix this path to extract! +bash "$BUILD_TOP/build/tool_source/solution_solvers/extract_projects.sh" "$proj_file" >"$CHECKERS" + +# iterate over all references in the project file. +while read line; do + grep -i "$line" "$found_solution" &>/dev/null + if [ $? -ne 0 ]; then + complain_about_project "$line" + fi +done <"$CHECKERS" + +if [ $errors_seen -eq 0 ]; then + echo "project $proj_base is clean." +else + echo "ERROR: there were $errors_seen problems in $proj_base; see above logging." + exit 3 +fi + +