diff options
Diffstat (limited to 'archive/2.vm/010vm.cc')
-rw-r--r-- | archive/2.vm/010vm.cc | 900 |
1 files changed, 900 insertions, 0 deletions
diff --git a/archive/2.vm/010vm.cc b/archive/2.vm/010vm.cc new file mode 100644 index 00000000..42b867fc --- /dev/null +++ b/archive/2.vm/010vm.cc @@ -0,0 +1,900 @@ +//: A Mu program is a book of 'recipes' (functions) +:(before "End Globals") +//: Each recipe is stored at a specific page number, or ordinal. +map<recipe_ordinal, recipe> Recipe; +//: You can also refer to each recipe by its name. +map<string, recipe_ordinal> Recipe_ordinal; +recipe_ordinal Next_recipe_ordinal = 1; + +//: Ordinals are like numbers, except you can't do arithmetic on them. Ordinal +//: 1 is not less than 2, it's just different. Phone numbers are ordinals; +//: adding two phone numbers is meaningless. Here each recipe does something +//: incommensurable with any other recipe. +:(after "Types") +typedef int recipe_ordinal; + +:(before "End Types") +// Recipes are lists of instructions. To perform or 'run' a recipe, the +// computer runs its instructions. +struct recipe { + recipe_ordinal ordinal; + string name; + vector<instruction> steps; + // End recipe Fields + recipe(); +}; + +:(before "struct recipe") +// Each instruction is either of the form: +// product1, product2, product3, ... <- operation ingredient1, ingredient2, ingredient3, ... +// or just a single 'label' starting with a non-alphanumeric character +// +label +// Labels don't do anything, they're just named locations in a recipe. +struct instruction { + bool is_label; + string label; // only if is_label + string name; // only if !is_label + string original_string; // for error messages + recipe_ordinal operation; // get(Recipe_ordinal, name) + vector<reagent> ingredients; // only if !is_label + vector<reagent> products; // only if !is_label + // End instruction Fields + instruction(); + void clear(); + bool is_empty(); +}; + +:(before "struct instruction") +// Ingredients and products are a single species -- a reagent. Reagents refer +// either to numbers or to locations in memory along with 'type' tags telling +// us how to interpret them. They also can contain arbitrary other lists of +// properties besides types, but we're getting ahead of ourselves. +struct reagent { + string original_string; + string name; + type_tree* type; + vector<pair<string, string_tree*> > properties; // can't be a map because the string_tree sometimes needs to be NULL, which can be confusing + double value; + bool initialized; + // End reagent Fields + reagent(const string& s); + reagent() :type(NULL), value(0), initialized(false) {} + reagent(type_tree* t) :type(t), value(0), initialized(false) {} + ~reagent(); + void clear(); + reagent(const reagent& original); + reagent& operator=(const reagent& original); + void set_value(double v) { value = v; initialized = true; } +}; + +:(before "struct reagent") +// Types can range from a simple type ordinal, to arbitrarily complex trees of +// type parameters, like (map (address array character) (list number)) +struct type_tree { + bool atom; + string name; // only if atom + type_ordinal value; // only if atom + type_tree* left; // only if !atom + type_tree* right; // only if !atom + ~type_tree(); + type_tree(const type_tree& original); + // atomic type ordinal + explicit type_tree(string name); + type_tree(string name, type_ordinal v) :atom(true), name(name), value(v), left(NULL), right(NULL) {} + // tree of type ordinals + type_tree(type_tree* l, type_tree* r) :atom(false), value(0), left(l), right(r) {} + type_tree& operator=(const type_tree& original); + bool operator==(const type_tree& other) const; + bool operator!=(const type_tree& other) const { return !operator==(other); } + bool operator<(const type_tree& other) const; + bool operator>(const type_tree& other) const { return other.operator<(*this); } +}; + +struct string_tree { + bool atom; + string value; // only if atom + string_tree* left; // only if !atom + string_tree* right; // only if !atom + ~string_tree(); + string_tree(const string_tree& original); + // atomic string + explicit string_tree(string v) :atom(true), value(v), left(NULL), right(NULL) {} + // tree of strings + string_tree(string_tree* l, string_tree* r) :atom(false), left(l), right(r) {} +}; + +// End type_tree Definition +:(code) +type_tree::type_tree(string name) :atom(true), name(name), value(get(Type_ordinal, name)), left(NULL), right(NULL) {} + +:(before "End Globals") +// Locations refer to a common 'memory'. Each location can store a number. +map<int, double> Memory; +:(before "End Reset") +Memory.clear(); + +:(after "Types") +// Mu types encode how the numbers stored in different parts of memory are +// interpreted. A location tagged as a 'character' type will interpret the +// value 97 as the letter 'a', while a different location of type 'number' +// would not. +// +// Unlike most computers today, Mu stores types in a single big table, shared +// by all the Mu programs on the computer. This is useful in providing a +// seamless experience to help understand arbitrary Mu programs. +typedef int type_ordinal; +:(before "End Globals") +map<string, type_ordinal> Type_ordinal; +map<type_ordinal, type_info> Type; +type_ordinal Next_type_ordinal = 1; +type_ordinal Number_type_ordinal = 0; +type_ordinal Boolean_type_ordinal = 0; +type_ordinal Character_type_ordinal = 0; +type_ordinal Address_type_ordinal = 0; +type_ordinal Array_type_ordinal = 0; +:(code) +void setup_types() { + Type.clear(); Type_ordinal.clear(); + put(Type_ordinal, "literal", 0); + Next_type_ordinal = 1; + // Mu Types Initialization + Number_type_ordinal = put(Type_ordinal, "number", Next_type_ordinal++); + get_or_insert(Type, Number_type_ordinal).name = "number"; + put(Type_ordinal, "location", Number_type_ordinal); // synonym of number for addresses we'll never look up + Address_type_ordinal = put(Type_ordinal, "address", Next_type_ordinal++); + get_or_insert(Type, Address_type_ordinal).name = "address"; + Boolean_type_ordinal = put(Type_ordinal, "boolean", Next_type_ordinal++); + get_or_insert(Type, Boolean_type_ordinal).name = "boolean"; + Character_type_ordinal = put(Type_ordinal, "character", Next_type_ordinal++); + get_or_insert(Type, Character_type_ordinal).name = "character"; + // Array types are a special modifier to any other type. For example, + // array:number or array:address:boolean. + Array_type_ordinal = put(Type_ordinal, "array", Next_type_ordinal++); + get_or_insert(Type, Array_type_ordinal).name = "array"; + // End Mu Types Initialization +} +void teardown_types() { + for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { + for (int i = 0; i < SIZE(p->second.elements); ++i) + p->second.elements.clear(); + } + Type_ordinal.clear(); +} +:(before "End One-time Setup") +setup_types(); +atexit(teardown_types); + +:(before "End Types") +// You can construct arbitrary new types. New types are either 'containers' +// with multiple 'elements' of other types, or 'exclusive containers' containing +// one of multiple 'variants'. (These are similar to C structs and unions, +// respectively, though exclusive containers implicitly include a tag element +// recording which variant they should be interpreted as.) +// +// For example, storing bank balance and name for an account might require a +// container, but if bank accounts may be either for individuals or groups, +// with different properties for each, that may require an exclusive container +// whose variants are individual-account and joint-account containers. +enum kind_of_type { + PRIMITIVE, + CONTAINER, + EXCLUSIVE_CONTAINER +}; + +struct type_info { + string name; + kind_of_type kind; + vector<reagent> elements; + // End type_info Fields + type_info() :kind(PRIMITIVE) { + // End type_info Constructor + } +}; + +enum primitive_recipes { + IDLE = 0, + COPY, + // End Primitive Recipe Declarations + MAX_PRIMITIVE_RECIPES, +}; +:(code) +//: It's all very well to construct recipes out of other recipes, but we need +//: to know how to do *something* out of the box. For the following +//: recipes there are only codes, no entries in the book, because Mu just knows +//: what to do for them. +void setup_recipes() { + Recipe.clear(); Recipe_ordinal.clear(); + put(Recipe_ordinal, "idle", IDLE); + // Primitive Recipe Numbers + put(Recipe_ordinal, "copy", COPY); + // End Primitive Recipe Numbers +} +//: We could just reset the recipe table after every test, but that gets slow +//: all too quickly. Instead, initialize the common stuff just once at +//: startup. Later layers will carefully undo each test's additions after +//: itself. +:(before "End One-time Setup") +setup_recipes(); +assert(MAX_PRIMITIVE_RECIPES < 200); // level 0 is primitives; until 199 +Next_recipe_ordinal = 200; +put(Recipe_ordinal, "main", Next_recipe_ordinal++); +// Load Mu Prelude +// End Mu Prelude +:(before "End Commandline Parsing") +assert(Next_recipe_ordinal < 1000); // recipes being tested didn't overflow into test space +:(before "End Reset") +Next_recipe_ordinal = 1000; // consistent new numbers for each test + +//: One final detail: tests can modify our global tables of recipes and types, +//: so we need some way to clean up after each test is done so it doesn't +//: influence later ones. +:(before "End Globals") +map<string, recipe_ordinal> Recipe_ordinal_snapshot; +map<recipe_ordinal, recipe> Recipe_snapshot; +map<string, type_ordinal> Type_ordinal_snapshot; +map<type_ordinal, type_info> Type_snapshot; +:(before "End One-time Setup") +save_snapshots(); +:(before "End Reset") +restore_snapshots(); + +:(code) +void save_snapshots() { + Recipe_ordinal_snapshot = Recipe_ordinal; + Recipe_snapshot = Recipe; + Type_ordinal_snapshot = Type_ordinal; + Type_snapshot = Type; + // End save_snapshots +} + +void restore_snapshots() { + Recipe = Recipe_snapshot; + Recipe_ordinal = Recipe_ordinal_snapshot; + restore_non_recipe_snapshots(); +} +// when running sandboxes in the edit/ app we'll want to restore everything except recipes defined in the app +void restore_non_recipe_snapshots() { + Type_ordinal = Type_ordinal_snapshot; + Type = Type_snapshot; + // End restore_snapshots +} + +//:: Helpers + +:(code) +recipe::recipe() { + ordinal = -1; + // End recipe Constructor +} + +instruction::instruction() :is_label(false), operation(IDLE) { + // End instruction Constructor +} +void instruction::clear() { + is_label=false; + label.clear(); + name.clear(); + operation=IDLE; + ingredients.clear(); + products.clear(); + original_string.clear(); + // End instruction Clear +} +bool instruction::is_empty() { return !is_label && name.empty(); } + +// Reagents have the form <name>:<type>:<type>:.../<property>/<property>/... +reagent::reagent(const string& s) :original_string(s), type(NULL), value(0), initialized(false) { + // Parsing reagent(string s) + istringstream in(s); + in >> std::noskipws; + // name and type + istringstream first_row(slurp_until(in, '/')); + first_row >> std::noskipws; + name = slurp_until(first_row, ':'); + string_tree* type_names = parse_property_list(first_row); + // End Parsing Reagent Type Property(type_names) + type = new_type_tree(type_names); + delete type_names; + // special cases + if (is_integer(name) && type == NULL) + type = new type_tree("literal"); + if (name == "_" && type == NULL) + type = new type_tree("literal"); + // other properties + slurp_properties(in, properties); + // End Parsing reagent +} + +void slurp_properties(istream& in, vector<pair<string, string_tree*> >& out) { + while (has_data(in)) { + istringstream row(slurp_until(in, '/')); + row >> std::noskipws; + string key = slurp_until(row, ':'); + string_tree* value = parse_property_list(row); + out.push_back(pair<string, string_tree*>(key, value)); + } +} + +string_tree* parse_property_list(istream& in) { + skip_whitespace_but_not_newline(in); + if (!has_data(in)) return NULL; + string_tree* first = new string_tree(slurp_until(in, ':')); + if (!has_data(in)) return first; + string_tree* rest = parse_property_list(in); + if (!has_data(in) && rest->atom) + return new string_tree(first, new string_tree(rest, NULL)); + return new string_tree(first, rest); +} +:(before "End Unit Tests") +void test_parse_property_list_atom() { + istringstream in("a"); + string_tree* x = parse_property_list(in); + CHECK(x->atom); + delete x; +} +void test_parse_property_list_list() { + istringstream in("a:b"); + string_tree* x = parse_property_list(in); + CHECK(!x->atom); + CHECK(x->left->atom); + CHECK_EQ(x->left->value, "a"); + CHECK(!x->right->atom); + CHECK(x->right->left->atom); + CHECK_EQ(x->right->left->value, "b"); + CHECK(x->right->right == NULL); + delete x; +} + +:(code) +type_tree* new_type_tree(const string_tree* properties) { + if (!properties) return NULL; + if (properties->atom) { + const string& type_name = properties->value; + int value = 0; + if (contains_key(Type_ordinal, type_name)) + value = get(Type_ordinal, type_name); + else if (is_integer(type_name)) // sometimes types will contain literal integers, like for the size of an array + value = 0; + else if (properties->value == "->") // used in recipe types + value = 0; + else + value = -1; // should never happen; will trigger errors later + return new type_tree(type_name, value); + } + return new type_tree(new_type_tree(properties->left), + new_type_tree(properties->right)); +} + +//: avoid memory leaks for the type tree + +reagent::reagent(const reagent& other) { + original_string = other.original_string; + name = other.name; + value = other.value; + initialized = other.initialized; + for (int i = 0; i < SIZE(other.properties); ++i) { + properties.push_back(pair<string, string_tree*>(other.properties.at(i).first, copy(other.properties.at(i).second))); + } + type = copy(other.type); + // End reagent Copy Constructor +} + +type_tree::type_tree(const type_tree& original) { + atom = original.atom; + name = original.name; + value = original.value; + left = copy(original.left); + right = copy(original.right); +} + +type_tree& type_tree::operator=(const type_tree& original) { + atom = original.atom; + name = original.name; + value = original.value; + if (left) delete left; + left = copy(original.left); + if (right) delete right; + right = copy(original.right); + return *this; +} + +bool type_tree::operator==(const type_tree& other) const { + if (atom != other.atom) return false; + if (atom) + return name == other.name && value == other.value; + return (left == other.left || *left == *other.left) + && (right == other.right || *right == *other.right); +} + +// only constraint we care about: if a < b then !(b < a) +bool type_tree::operator<(const type_tree& other) const { + if (atom != other.atom) return atom > other.atom; // atoms before non-atoms + if (atom) return value < other.value; + // first location in one that's missing in the other makes that side 'smaller' + if (left && !other.left) return false; + if (!left && other.left) return true; + if (right && !other.right) return false; + if (!right && other.right) return true; + // now if either pointer is unequal neither side can be null + // if one side is equal that's easy + if (left == other.left || *left == *other.left) return right && *right < *other.right; + if (right == other.right || *right == *other.right) return left && *left < *other.left; + // if the two sides criss-cross, pick the side with the smaller lhs + if ((left == other.right || *left == *other.right) + && (right == other.left || *right == *other.left)) + return *left < *other.left; + // now the hard case: both sides are not equal + // make sure we stay consistent between (a < b) and (b < a) + // just return the side with the smallest of the 4 branches + if (*left < *other.left && *left < *other.right) return true; + if (*right < *other.left && *right < *other.right) return true; + return false; +} +:(before "End Unit Tests") +// These unit tests don't always use valid types. +void test_compare_atom_types() { + reagent a("a:address"), b("b:boolean"); + CHECK(*a.type < *b.type); + CHECK(!(*b.type < *a.type)); +} +void test_compare_equal_atom_types() { + reagent a("a:address"), b("b:address"); + CHECK(!(*a.type < *b.type)); + CHECK(!(*b.type < *a.type)); +} +void test_compare_atom_with_non_atom() { + reagent a("a:address:number"), b("b:boolean"); + CHECK(!(*a.type < *b.type)); + CHECK(*b.type < *a.type); +} +void test_compare_lists_with_identical_structure() { + reagent a("a:address:address"), b("b:address:boolean"); + CHECK(*a.type < *b.type); + CHECK(!(*b.type < *a.type)); +} +void test_compare_identical_lists() { + reagent a("a:address:boolean"), b("b:address:boolean"); + CHECK(!(*a.type < *b.type)); + CHECK(!(*b.type < *a.type)); +} +void test_compare_list_with_extra_element() { + reagent a("a:address:address"), b("b:address:address:number"); + CHECK(*a.type < *b.type); + CHECK(!(*b.type < *a.type)); +} +void test_compare_list_with_smaller_left_but_larger_right() { + reagent a("a:number:character"), b("b:boolean:array"); + CHECK(*a.type < *b.type); + CHECK(!(*b.type < *a.type)); +} +void test_compare_list_with_smaller_left_but_larger_right_identical_types() { + reagent a("a:address:boolean"), b("b:boolean:address"); + CHECK(*a.type < *b.type); + CHECK(!(*b.type < *a.type)); +} + +:(code) +string_tree::string_tree(const string_tree& original) { + atom = original.atom; + value = original.value; + left = copy(original.left); + right = copy(original.right); +} + +reagent& reagent::operator=(const reagent& other) { + original_string = other.original_string; + for (int i = 0; i < SIZE(properties); ++i) + if (properties.at(i).second) delete properties.at(i).second; + properties.clear(); + for (int i = 0; i < SIZE(other.properties); ++i) + properties.push_back(pair<string, string_tree*>(other.properties.at(i).first, copy(other.properties.at(i).second))); + name = other.name; + value = other.value; + initialized = other.initialized; + if (type) delete type; + type = copy(other.type); + // End reagent Copy Operator + return *this; +} + +reagent::~reagent() { + clear(); +} + +void reagent::clear() { + for (int i = 0; i < SIZE(properties); ++i) { + if (properties.at(i).second) { + delete properties.at(i).second; + properties.at(i).second = NULL; + } + } + delete type; + type = NULL; +} +type_tree::~type_tree() { + delete left; + delete right; +} +string_tree::~string_tree() { + delete left; + delete right; +} + +void append(type_tree*& base, type_tree* extra) { + if (!base) { + base = extra; + return; + } + type_tree* curr = base; + while (curr->right) curr = curr->right; + curr->right = extra; +} + +void append(string_tree*& base, string_tree* extra) { + if (!base) { + base = extra; + return; + } + string_tree* curr = base; + while (curr->right) curr = curr->right; + curr->right = extra; +} + +string slurp_until(istream& in, char delim) { + ostringstream out; + char c; + while (in >> c) { + if (c == delim) { + // drop the delim + break; + } + out << c; + } + return out.str(); +} + +bool has_property(const reagent& x, const string& name) { + for (int i = 0; i < SIZE(x.properties); ++i) { + if (x.properties.at(i).first == name) return true; + } + return false; +} + +string_tree* property(const reagent& r, const string& name) { + for (int p = 0; p != SIZE(r.properties); ++p) { + if (r.properties.at(p).first == name) + return r.properties.at(p).second; + } + return NULL; +} + +string_tree* copy(const string_tree* x) { + if (x == NULL) return NULL; + return new string_tree(*x); +} + +type_tree* copy(const type_tree* x) { + if (x == NULL) return NULL; + return new type_tree(*x); +} + +:(before "End Globals") +extern const string Ignore(","); // commas are ignored in Mu except within [] strings +:(code) +void skip_whitespace_but_not_newline(istream& in) { + while (true) { + if (!has_data(in)) break; + else if (in.peek() == '\n') break; + else if (isspace(in.peek())) in.get(); + else if (Ignore.find(in.peek()) != string::npos) in.get(); + else break; + } +} + +void dump_memory() { + for (map<int, double>::iterator p = Memory.begin(); p != Memory.end(); ++p) { + cerr << p->first << ": " << no_scientific(p->second) << '\n'; + } +} + +//:: Helpers for converting various values to string +//: Use to_string() in trace(), and try to keep it stable from run to run. +//: Use debug_string() while debugging, and throw everything into it. +//: Use inspect() only for emitting a canonical format that can be parsed back +//: into the value. + +string to_string(const recipe& r) { + ostringstream out; + out << "recipe " << r.name << " [\n"; + for (int i = 0; i < SIZE(r.steps); ++i) + out << " " << to_string(r.steps.at(i)) << '\n'; + out << "]\n"; + return out.str(); +} + +string to_original_string(const recipe& r) { + ostringstream out; + out << "recipe " << r.name << " [\n"; + for (int i = 0; i < SIZE(r.steps); ++i) + out << " " << to_original_string(r.steps.at(i)) << '\n'; + out << "]\n"; + return out.str(); +} + +string debug_string(const recipe& x) { + ostringstream out; + out << "- recipe " << x.name << '\n'; + // Begin debug_string(recipe x) + for (int index = 0; index < SIZE(x.steps); ++index) { + const instruction& inst = x.steps.at(index); + out << "inst: " << to_string(inst) << '\n'; + out << " ingredients\n"; + for (int i = 0; i < SIZE(inst.ingredients); ++i) + out << " " << debug_string(inst.ingredients.at(i)) << '\n'; + out << " products\n"; + for (int i = 0; i < SIZE(inst.products); ++i) + out << " " << debug_string(inst.products.at(i)) << '\n'; + } + return out.str(); +} + +string to_original_string(const instruction& inst) { + if (inst.is_label) return inst.label; + if (!inst.original_string.empty()) return inst.original_string; + ostringstream out; + for (int i = 0; i < SIZE(inst.products); ++i) { + if (i > 0) out << ", "; + out << inst.products.at(i).original_string; + } + if (!inst.products.empty()) out << " <- "; + out << inst.name; + if (!inst.ingredients.empty()) out << ' '; + for (int i = 0; i < SIZE(inst.ingredients); ++i) { + if (i > 0) out << ", "; + out << inst.ingredients.at(i).original_string; + } + return out.str(); +} + +string to_string(const instruction& inst) { + if (inst.is_label) return inst.label; + ostringstream out; + for (int i = 0; i < SIZE(inst.products); ++i) { + if (i > 0) out << ", "; + out << to_string(inst.products.at(i)); + } + if (!inst.products.empty()) out << " <- "; + out << inst.name << ' '; + for (int i = 0; i < SIZE(inst.ingredients); ++i) { + if (i > 0) out << ", "; + out << to_string(inst.ingredients.at(i)); + } + return out.str(); +} + +string to_string(const reagent& r) { + if (is_dummy(r)) return "_"; + ostringstream out; + out << "{"; + out << r.name << ": " << names_to_string(r.type); + if (!r.properties.empty()) { + for (int i = 0; i < SIZE(r.properties); ++i) + out << ", \"" << r.properties.at(i).first << "\": " << to_string(r.properties.at(i).second); + } + out << "}"; + return out.str(); +} + +// special name for ignoring some products +bool is_dummy(const reagent& x) { + return x.name == "_"; +} + +string debug_string(const reagent& x) { + ostringstream out; + out << x.name << ": " << x.value << ' ' << to_string(x.type) << " -- " << to_string(x); + return out.str(); +} + +string to_string(const string_tree* property) { + if (!property) return "()"; + ostringstream out; + dump(property, out); + return out.str(); +} + +void dump(const string_tree* x, ostream& out) { + if (!x) return; + if (x->atom) { + out << '"' << x->value << '"'; + return; + } + out << '('; + const string_tree* curr = x; + while (curr && !curr->atom) { + dump(curr->left, out); + if (curr->right) out << ' '; + curr = curr->right; + } + // check for dotted list; should never happen + if (curr) { + out << ". "; + dump(curr, out); + } + out << ')'; +} + +string to_string(const type_tree* type) { + if (type == NULL) return "()"; + ostringstream out; + dump(type, out); + return out.str(); +} + +void dump(const type_tree* x, ostream& out) { + if (!x) return; + if (x->atom) { + dump(x->value, out); + return; + } + out << '('; + const type_tree* curr = x; + while (curr && !curr->atom) { + dump(curr->left, out); + if (curr->right) out << ' '; + curr = curr->right; + } + // check for dotted list; should never happen + if (curr) { + out << ". "; + dump(curr, out); + } + out << ')'; +} + +void dump(type_ordinal type, ostream& out) { + if (contains_key(Type, type)) + out << get(Type, type).name; + else + out << "?" << type; +} + +string names_to_string(const type_tree* type) { + if (type == NULL) return "()"; // should never happen + ostringstream out; + dump_names(type, out); + return out.str(); +} + +void dump_names(const type_tree* x, ostream& out) { + if (!x) return; + if (x->atom) { + out << '"' << x->name << '"'; + return; + } + out << '('; + const type_tree* curr = x; + while (curr && !curr->atom) { + dump_names(curr->left, out); + if (curr->right) out << ' '; + curr = curr->right; + } + // check for dotted list; should never happen + if (curr) { + out << ". "; + dump_names(curr, out); + } + out << ')'; +} + +string names_to_string_without_quotes(const type_tree* type) { + if (type == NULL) return "()"; + ostringstream out; + dump_names_without_quotes(type, out); + return out.str(); +} + +void dump_names_without_quotes(const type_tree* x, ostream& out) { + if (!x) return; + if (x->atom) { + out << x->name; + return; + } + out << '('; + const type_tree* curr = x; + while (curr && !curr->atom) { + dump_names_without_quotes(curr->left, out); + if (curr->right) out << ' '; + curr = curr->right; + } + // check for dotted list; should never happen + if (curr) { + out << ". "; + dump_names_without_quotes(curr, out); + } + out << ')'; +} + +bool is_integer(const string& s) { + return s.find_first_not_of("0123456789-") == string::npos // no other characters + && s.find_first_of("0123456789") != string::npos // at least one digit + && s.find('-', 1) == string::npos; // '-' only at first position +} + +int to_integer(string n) { + char* end = NULL; + // safe because string.c_str() is guaranteed to be null-terminated + int result = strtoll(n.c_str(), &end, /*any base*/0); + if (*end != '\0') cerr << "tried to convert " << n << " to number\n"; + assert(*end == '\0'); + return result; +} + +void test_is_integer() { + CHECK(is_integer("1234")); + CHECK(is_integer("-1")); + CHECK(!is_integer("234.0")); + CHECK(is_integer("-567")); + CHECK(!is_integer("89-0")); + CHECK(!is_integer("-")); + CHECK(!is_integer("1e3")); // not supported +} + +//: helper to print numbers without excessive precision + +:(before "End Types") +struct no_scientific { + double x; + explicit no_scientific(double y) :x(y) {} +}; + +:(code) +ostream& operator<<(ostream& os, no_scientific x) { + if (!isfinite(x.x)) { + // Infinity or NaN + os << x.x; + return os; + } + ostringstream tmp; + // more accurate, but too slow +//? tmp.precision(308); // for 64-bit numbers + tmp << std::fixed << x.x; + os << trim_floating_point(tmp.str()); + return os; +} + +string trim_floating_point(const string& in) { + if (in.empty()) return ""; + if (in.find('.') == string::npos) return in; + int length = SIZE(in); + while (length > 1) { + if (in.at(length-1) != '0') break; + --length; + } + if (in.at(length-1) == '.') --length; + if (length == 0) return "0"; + return in.substr(0, length); +} + +void test_trim_floating_point() { + CHECK_EQ(trim_floating_point(""), ""); + CHECK_EQ(trim_floating_point(".0"), "0"); + CHECK_EQ(trim_floating_point("1.5000"), "1.5"); + CHECK_EQ(trim_floating_point("1.000001"), "1.000001"); + CHECK_EQ(trim_floating_point("23.000000"), "23"); + CHECK_EQ(trim_floating_point("23.0"), "23"); + CHECK_EQ(trim_floating_point("23."), "23"); + CHECK_EQ(trim_floating_point("23"), "23"); + CHECK_EQ(trim_floating_point("230"), "230"); + CHECK_EQ(trim_floating_point("3.000000"), "3"); + CHECK_EQ(trim_floating_point("3.0"), "3"); + CHECK_EQ(trim_floating_point("3."), "3"); + CHECK_EQ(trim_floating_point("3"), "3"); +} + +:(before "End Includes") +#include <map> +using std::map; +#include <utility> +using std::pair; +#include <math.h> |