1
2 :(before "End Globals")
3
4 map<recipe_ordinal, recipe> Recipe;
5
6 map<string, recipe_ordinal> Recipe_ordinal;
7 recipe_ordinal Next_recipe_ordinal = 1;
8
9
10
11
12
13 :(after "Types")
14 typedef int recipe_ordinal;
15
16 :(before "End Types")
17
18
19 struct recipe {
20 string name;
21 vector<instruction> steps;
22
23 recipe();
24 };
25
26 :(before "struct recipe")
27
28
29
30
31
32 struct instruction {
33 bool is_label;
34 string label;
35 string name;
36 string original_string;
37 recipe_ordinal operation;
38 vector<reagent> ingredients;
39 vector<reagent> products;
40
41 instruction();
42 void clear();
43 bool is_empty();
44 };
45
46 :(before "struct instruction")
47
48
49
50
51 struct reagent {
52 string original_string;
53 string name;
54 type_tree* type;
55 vector<pair<string, string_tree*> > properties;
56 double value;
57 bool initialized;
58
59 reagent(const string& s);
60 reagent() :type(NULL), value(0), initialized(false) {}
61 ~reagent();
62 void clear();
63 reagent(const reagent& original);
64 reagent& operator=(const reagent& original);
65 void set_value(double v) { value = v; initialized = true; }
66 };
67
68 :(before "struct reagent")
69
70
71 struct type_tree {
72 bool atom;
73 string name;
74 type_ordinal value;
75 type_tree* left;
76 type_tree* right;
77 ~type_tree();
78 type_tree(const type_tree& original);
79
80 explicit type_tree(string name);
81 type_tree(string name, type_ordinal v) :atom(true), name(name), value(v), left(NULL), right(NULL) {}
82
83 type_tree(type_tree* l, type_tree* r) :atom(false), value(0), left(l), right(r) {}
84 type_tree& operator=(const type_tree& original);
85 bool operator==(const type_tree& other) const;
86 bool operator!=(const type_tree& other) const { return !operator==(other); }
87 bool operator<(const type_tree& other) const;
88 bool operator>(const type_tree& other) const { return other.operator<(*this); }
89 };
90
91 struct string_tree {
92 bool atom;
93 string value;
94 string_tree* left;
95 string_tree* right;
96 ~string_tree();
97 string_tree(const string_tree& original);
98
99 explicit string_tree(string v) :atom(true), value(v), left(NULL), right(NULL) {}
100
101 string_tree(string_tree* l, string_tree* r) :atom(false), left(l), right(r) {}
102 };
103
104
105 :(code)
106 type_tree::type_tree(string name) :atom(true), name(name), value(get(Type_ordinal, name)), left(NULL), right(NULL) {}
107
108 :(before "End Globals")
109
110 map<int, double> Memory;
111 :(before "End Setup")
112 Memory.clear();
113
114 :(after "Types")
115
116
117
118
119
120
121
122
123 typedef int type_ordinal;
124 :(before "End Globals")
125 map<string, type_ordinal> Type_ordinal;
126 map<type_ordinal, type_info> Type;
127 type_ordinal Next_type_ordinal = 1;
128 :(code)
129 void setup_types() {
130 Type.clear(); Type_ordinal.clear();
131 put(Type_ordinal, "literal", 0);
132 Next_type_ordinal = 1;
133
134 type_ordinal number = put(Type_ordinal, "number", Next_type_ordinal++);
135 get_or_insert(Type, number).name = "number";
136 put(Type_ordinal, "location", number);
137 type_ordinal address = put(Type_ordinal, "address", Next_type_ordinal++);
138 get_or_insert(Type, address).name = "address";
139 type_ordinal boolean = put(Type_ordinal, "boolean", Next_type_ordinal++);
140 get_or_insert(Type, boolean).name = "boolean";
141 type_ordinal character = put(Type_ordinal, "character", Next_type_ordinal++);
142 get_or_insert(Type, character).name = "character";
143
144
145 type_ordinal array = put(Type_ordinal, "array", Next_type_ordinal++);
146 get_or_insert(Type, array).name = "array";
147
148 }
149 void teardown_types() {
150 for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) {
151 ¦ for (int i = 0; i < SIZE(p->second.elements); ++i)
152 ¦ ¦ p->second.elements.clear();
153 }
154 Type_ordinal.clear();
155 }
156 :(before "End One-time Setup")
157 setup_types();
158 atexit(teardown_types);
159
160 :(before "End Types")
161
162
163
164
165
166
167
168
169
170
171 enum kind_of_type {
172 PRIMITIVE,
173 CONTAINER,
174 EXCLUSIVE_CONTAINER
175 };
176
177 struct type_info {
178 string name;
179 kind_of_type kind;
180 vector<reagent> elements;
181
182 type_info() :kind(PRIMITIVE) {
183 ¦
184 }
185 };
186
187 enum primitive_recipes {
188 IDLE = 0,
189 COPY,
190
191 MAX_PRIMITIVE_RECIPES,
192 };
193 :(code)
194
195
196
197
198 void setup_recipes() {
199 Recipe.clear(); Recipe_ordinal.clear();
200 put(Recipe_ordinal, "idle", IDLE);
201
202 put(Recipe_ordinal, "copy", COPY);
203
204 }
205
206
207
208
209 :(before "End One-time Setup")
210 setup_recipes();
211 assert(MAX_PRIMITIVE_RECIPES < 200);
212 Next_recipe_ordinal = 200;
213 put(Recipe_ordinal, "main", Next_recipe_ordinal++);
214
215 :(before "End Commandline Parsing")
216 assert(Next_recipe_ordinal < 1000);
217 :(before "End Setup")
218 Next_recipe_ordinal = 1000;
219
220
221
222
223 :(before "End Globals")
224 map<string, recipe_ordinal> Recipe_ordinal_snapshot;
225 map<recipe_ordinal, recipe> Recipe_snapshot;
226 map<string, type_ordinal> Type_ordinal_snapshot;
227 map<type_ordinal, type_info> Type_snapshot;
228 :(before "End One-time Setup")
229 save_snapshots();
230 :(before "End Setup")
231 restore_snapshots();
232
233 :(code)
234 void save_snapshots() {
235 Recipe_ordinal_snapshot = Recipe_ordinal;
236 Recipe_snapshot = Recipe;
237 Type_ordinal_snapshot = Type_ordinal;
238 Type_snapshot = Type;
239
240 }
241
242 void restore_snapshots() {
243 Recipe = Recipe_snapshot;
244 Recipe_ordinal = Recipe_ordinal_snapshot;
245 restore_non_recipe_snapshots();
246 }
247
248 void restore_non_recipe_snapshots() {
249 Type_ordinal = Type_ordinal_snapshot;
250 Type = Type_snapshot;
251
252 }
253
254
255
256 :(code)
257 recipe::recipe() {
258
259 }
260
261 instruction::instruction() :is_label(false), operation(IDLE) {
262
263 }
264 void instruction::clear() {
265 is_label=false;
266 label.clear();
267 name.clear();
268 operation=IDLE;
269 ingredients.clear();
270 products.clear();
271 original_string.clear();
272
273 }
274 bool instruction::is_empty() { return !is_label && name.empty(); }
275
276
277 reagent::reagent(const string& s) :original_string(s), type(NULL), value(0), initialized(false) {
278
279 istringstream in(s);
280 in >> std::noskipws;
281
282 istringstream first_row(slurp_until(in, '/'));
283 first_row >> std::noskipws;
284 name = slurp_until(first_row, ':');
285 string_tree* type_names = parse_property_list(first_row);
286
287 type = new_type_tree(type_names);
288 delete type_names;
289
290 if (is_integer(name) && type == NULL)
291 ¦ type = new type_tree("literal");
292 if (name == "_" && type == NULL)
293 ¦ type = new type_tree("literal");
294
295 slurp_properties(in, properties);
296
297 }
298
299 void slurp_properties(istream& in, vector<pair<string, string_tree*> >& out) {
300 while (has_data(in)) {
301 ¦ istringstream row(slurp_until(in, '/'));
302 ¦ row >> std::noskipws;
303 ¦ string key = slurp_until(row, ':');
304 ¦ string_tree* value = parse_property_list(row);
305 ¦ out.push_back(pair<string, string_tree*>(key, value));
306 }
307 }
308
309 string_tree* parse_property_list(istream& in) {
310 skip_whitespace_but_not_newline(in);
311 if (!has_data(in)) return NULL;
312 string_tree* first = new string_tree(slurp_until(in, ':'));
313 if (!has_data(in)) return first;
314 string_tree* rest = parse_property_list(in);
315 if (!has_data(in) && rest->atom)
316 ¦ return new string_tree(first, new string_tree(rest, NULL));
317 return new string_tree(first, rest);
318 }
319 :(before "End Unit Tests")
320 void test_parse_property_list_atom() {
321 istringstream in("a");
322 string_tree* x = parse_property_list(in);
323 CHECK(x->atom);
324 delete x;
325 }
326 void test_parse_property_list_list() {
327 istringstream in("a:b");
328 string_tree* x = parse_property_list(in);
329 CHECK(!x->atom);
330 CHECK(x->left->atom);
331 CHECK_EQ(x->left->value, "a");
332 CHECK(!x->right->atom);
333 CHECK(x->right->left->atom);
334 CHECK_EQ(x->right->left->value, "b");
335 CHECK(x->right->right == NULL);
336 delete x;
337 }
338
339 :(code)
340 type_tree* new_type_tree(const string_tree* properties) {
341 if (!properties) return NULL;
342 if (properties->atom) {
343 ¦ const string& type_name = properties->value;
344 ¦ int value = 0;
345 ¦ if (contains_key(Type_ordinal, type_name))
346 ¦ ¦ value = get(Type_ordinal, type_name);
347 ¦ else if (is_integer(type_name))
348 ¦ ¦ value = 0;
349 ¦ else if (properties->value == "->")
350 ¦ ¦ value = 0;
351 ¦ else
352 ¦ ¦ value = -1;
353 ¦ return new type_tree(type_name, value);
354 }
355 return new type_tree(new_type_tree(properties->left),
356 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ new_type_tree(properties->right));
357 }
358
359
360
361 reagent::reagent(const reagent& other) {
362 original_string = other.original_string;
363 name = other.name;
364 value = other.value;
365 initialized = other.initialized;
366 for (int i = 0; i < SIZE(other.properties); ++i) {
367 ¦ properties.push_back(pair<string, string_tree*>(other.properties.at(i).first, copy(other.properties.at(i).second)));
368 }
369 type = copy(other.type);
370
371 }
372
373 type_tree::type_tree(const type_tree& original) {
374 atom = original.atom;
375 name = original.name;
376 value = original.value;
377 left = copy(original.left);
378 right = copy(original.right);
379 }
380
381 type_tree& type_tree::operator=(const type_tree& original) {
382 atom = original.atom;
383 name = original.name;
384 value = original.value;
385 if (left) delete left;
386 left = copy(original.left);
387 if (right) delete right;
388 right = copy(original.right);
389 return *this;
390 }
391
392 bool type_tree::operator==(const type_tree& other) const {
393 if (atom != other.atom) return false;
394 if (atom)
395 ¦ return name == other.name && value == other.value;
396 return (left == other.left || *left == *other.left)
397 ¦ ¦ && (right == other.right || *right == *other.right);
398 }
399
400
401 bool type_tree::operator<(const type_tree& other) const {
402 if (atom != other.atom) return atom > other.atom;
403 if (atom) return name < other.name;
404
405 if (left && !other.left) return false;
406 if (!left && other.left) return true;
407 if (right && !other.right) return false;
408 if (!right && other.right) return true;
409
410
411 if (left == other.left || *left == *other.left) return right && *right < *other.right;
412 if (right == other.right || *right == *other.right) return left && *left < *other.left;
413
414 if ((left == other.right || *left == *other.right)
415 ¦ ¦ && (right == other.left || *right == *other.left))
416 ¦ return *left < *other.left;
417
418
419
420 if (*left < *other.left && *left < *other.right) return true;
421 if (*right < *other.left && *right < *other.right) return true;
422 return false;
423 }
424 :(before "End Unit Tests")
425
426 void test_compare_atom_types() {
427 reagent a("a:address"), b("b:boolean");
428 CHECK(*a.type < *b.type);
429 CHECK(!(*b.type < *a.type));
430 }
431 void test_compare_equal_atom_types() {
432 reagent a("a:address"), b("b:address");
433 CHECK(!(*a.type < *b.type));
434 CHECK(!(*b.type < *a.type));
435 }
436 void test_compare_atom_with_non_atom() {
437 reagent a("a:address:number"), b("b:boolean");
438 CHECK(!(*a.type < *b.type));
439 CHECK(*b.type < *a.type);
440 }
441 void test_compare_lists_with_identical_structure() {
442 reagent a("a:address:address"), b("b:address:boolean");
443 CHECK(*a.type < *b.type);
444 CHECK(!(*b.type < *a.type));
445 }
446 void test_compare_identical_lists() {
447 reagent a("a:address:boolean"), b("b:address:boolean");
448 CHECK(!(*a.type < *b.type));
449 CHECK(!(*b.type < *a.type));
450 }
451 void test_compare_list_with_extra_element() {
452 reagent a("a:address:address"), b("b:address:address:number");
453 CHECK(*a.type < *b.type);
454 CHECK(!(*b.type < *a.type));
455 }
456 void test_compare_list_with_smaller_left_but_larger_right() {
457 reagent a("a:address:number"), b("b:character:array");
458 CHECK(*a.type < *b.type);
459 CHECK(!(*b.type < *a.type));
460 }
461 void test_compare_list_with_smaller_left_but_larger_right_identical_types() {
462 reagent a("a:address:boolean"), b("b:boolean:address");
463 CHECK(*a.type < *b.type);
464 CHECK(!(*b.type < *a.type));
465 }
466
467 :(code)
468 string_tree::string_tree(const string_tree& original) {
469 atom = original.atom;
470 value = original.value;
471 left = copy(original.left);
472 right = copy(original.right);
473 }
474
475 reagent& reagent::operator=(const reagent& other) {
476 original_string = other.original_string;
477 for (int i = 0; i < SIZE(properties); ++i)
478 ¦ if (properties.at(i).second) delete properties.at(i).second;
479 properties.clear();
480 for (int i = 0; i < SIZE(other.properties); ++i)
481 ¦ properties.push_back(pair<string, string_tree*>(other.properties.at(i).first, copy(other.properties.at(i).second)));
482 name = other.name;
483 value = other.value;
484 initialized = other.initialized;
485 if (type) delete type;
486 type = copy(other.type);
487
488 return *this;
489 }
490
491 reagent::~reagent() {
492 clear();
493 }
494
495 void reagent::clear() {
496 for (int i = 0; i < SIZE(properties); ++i) {
497 ¦ if (properties.at(i).second) {
498 ¦ ¦ delete properties.at(i).second;
499 ¦ ¦ properties.at(i).second = NULL;
500 ¦ }
501 }
502 delete type;
503 type = NULL;
504 }
505 type_tree::~type_tree() {
506 delete left;
507 delete right;
508 }
509 string_tree::~string_tree() {
510 delete left;
511 delete right;
512 }
513
514 void append(type_tree*& base, type_tree* extra) {
515 if (!base) {
516 ¦ base = extra;
517 ¦ return;
518 }
519 type_tree* curr = base;
520 while (curr->right) curr = curr->right;
521 curr->right = extra;
522 }
523
524 void append(string_tree*& base, string_tree* extra) {
525 if (!base) {
526 ¦ base = extra;
527 ¦ return;
528 }
529 string_tree* curr = base;
530 while (curr->right) curr = curr->right;
531 curr->right = extra;
532 }
533
534 string slurp_until(istream& in, char delim) {
535 ostringstream out;
536 char c;
537 while (in >> c) {
538 ¦ if (c == delim) {
539 ¦ ¦
540 ¦ ¦ break;
541 ¦ }
542 ¦ out << c;
543 }
544 return out.str();
545 }
546
547 bool has_property(const reagent& x, const string& name) {
548 for (int i = 0; i < SIZE(x.properties); ++i) {
549 ¦ if (x.properties.at(i).first == name) return true;
550 }
551 return false;
552 }
553
554 string_tree* property(const reagent& r, const string& name) {
555 for (int p = 0; p != SIZE(r.properties); ++p) {
556 ¦ if (r.properties.at(p).first == name)
557 ¦ ¦ return r.properties.at(p).second;
558 }
559 return NULL;
560 }
561
562 string_tree* copy(const string_tree* x) {
563 if (x == NULL) return NULL;
564 return new string_tree(*x);
565 }
566
567 type_tree* copy(const type_tree* x) {
568 if (x == NULL) return NULL;
569 return new type_tree(*x);
570 }
571
572 :(before "End Globals")
573 extern const string Ignore(",");
574 :(code)
575 void skip_whitespace_but_not_newline(istream& in) {
576 while (true) {
577 ¦ if (!has_data(in)) break;
578 ¦ else if (in.peek() == '\n') break;
579 ¦ else if (isspace(in.peek())) in.get();
580 ¦ else if (Ignore.find(in.peek()) != string::npos) in.get();
581 ¦ else break;
582 }
583 }
584
585 void dump_memory() {
586 for (map<int, double>::iterator p = Memory.begin(); p != Memory.end(); ++p) {
587 ¦ cout << p->first << ": " << no_scientific(p->second) << '\n';
588 }
589 }
590
591
592
593
594
595
596
597 string to_string(const recipe& r) {
598 ostringstream out;
599 out << "recipe " << r.name << " [\n";
600 for (int i = 0; i < SIZE(r.steps); ++i)
601 ¦ out << " " << to_string(r.steps.at(i)) << '\n';
602 out << "]\n";
603 return out.str();
604 }
605
606 string to_original_string(const recipe& r) {
607 ostringstream out;
608 out << "recipe " << r.name << " [\n";
609 for (int i = 0; i < SIZE(r.steps); ++i)
610 ¦ out << " " << to_original_string(r.steps.at(i)) << '\n';
611 out << "]\n";
612 return out.str();
613 }
614
615 string debug_string(const recipe& x) {
616 ostringstream out;
617 out << "- recipe " << x.name << '\n';
618
619 for (int index = 0; index < SIZE(x.steps); ++index) {
620 ¦ const instruction& inst = x.steps.at(index);
621 ¦ out << "inst: " << to_string(inst) << '\n';
622 ¦ out << " ingredients\n";
623 ¦ for (int i = 0; i < SIZE(inst.ingredients); ++i)
624 ¦ ¦ out << " " << debug_string(inst.ingredients.at(i)) << '\n';
625 ¦ out << " products\n";
626 ¦ for (int i = 0; i < SIZE(inst.products); ++i)
627 ¦ ¦ out << " " << debug_string(inst.products.at(i)) << '\n';
628 }
629 return out.str();
630 }
631
632 string to_original_string(const instruction& inst) {
633 if (inst.is_label) return inst.label;
634 if (!inst.original_string.empty()) return inst.original_string;
635 ostringstream out;
636 for (int i = 0; i < SIZE(inst.products); ++i) {
637 ¦ if (i > 0) out << ", ";
638 ¦ out << inst.products.at(i).original_string;
639 }
640 if (!inst.products.empty()) out << " <- ";
641 out << inst.name;
642 if (!inst.ingredients.empty()) out << ' ';
643 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
644 ¦ if (i > 0) out << ", ";
645 ¦ out << inst.ingredients.at(i).original_string;
646 }
647 return out.str();
648 }
649
650 string to_string(const instruction& inst) {
651 if (inst.is_label) return inst.label;
652 ostringstream out;
653 for (int i = 0; i < SIZE(inst.products); ++i) {
654 ¦ if (i > 0) out << ", ";
655 ¦ out << to_string(inst.products.at(i));
656 }
657 if (!inst.products.empty()) out << " <- ";
658 out << inst.name << ' ';
659 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
660 ¦ if (i > 0) out << ", ";
661 ¦ out << to_string(inst.ingredients.at(i));
662 }
663 return out.str();
664 }
665
666 string to_string(const reagent& r) {
667 if (is_dummy(r)) return "_";
668 ostringstream out;
669 out << "{";
670 out << r.name << ": " << names_to_string(r.type);
671 if (!r.properties.empty()) {
672 ¦ for (int i = 0; i < SIZE(r.properties); ++i)
673 ¦ ¦ out << ", \"" << r.properties.at(i).first << "\": " << to_string(r.properties.at(i).second);
674 }
675 out << "}";
676 return out.str();
677 }
678
679
680 bool is_dummy(const reagent& x) {
681 return x.name == "_";
682 }
683
684 string debug_string(const reagent& x) {
685 ostringstream out;
686 out << x.name << ": " << x.value << ' ' << to_string(x.type) << " -- " << to_string(x);
687 return out.str();
688 }
689
690 string to_string(const string_tree* property) {
691 if (!property) return "()";
692 ostringstream out;
693 dump(property, out);
694 return out.str();
695 }
696
697 void dump(const string_tree* x, ostream& out) {
698 if (!x) return;
699 if (x->atom) {
700 ¦ out << '"' << x->value << '"';
701 ¦ return;
702 }
703 out << '(';
704 const string_tree* curr = x;
705 while (curr && !curr->atom) {
706 ¦ dump(curr->left, out);
707 ¦ if (curr->right) out << ' ';
708 ¦ curr = curr->right;
709 }
710
711 if (curr) {
712 ¦ out << ". ";
713 ¦ dump(curr, out);
714 }
715 out << ')';
716 }
717
718 string to_string(const type_tree* type) {
719 if (type == NULL) return "()";
720 ostringstream out;
721 dump(type, out);
722 return out.str();
723 }
724
725 void dump(const type_tree* x, ostream& out) {
726 if (!x) return;
727 if (x->atom) {
728 ¦ dump(x->value, out);
729 ¦ return;
730 }
731 out << '(';
732 const type_tree* curr = x;
733 while (curr && !curr->atom) {
734 ¦ dump(curr->left, out);
735 ¦ if (curr->right) out << ' ';
736 ¦ curr = curr->right;
737 }
738
739 if (curr) {
740 ¦ out << ". ";
741 ¦ dump(curr, out);
742 }
743 out << ')';
744 }
745
746 void dump(type_ordinal type, ostream& out) {
747 if (contains_key(Type, type))
748 ¦ out << get(Type, type).name;
749 else
750 ¦ out << "?" << type;
751 }
752
753 string names_to_string(const type_tree* type) {
754 if (type == NULL) return "()";
755 ostringstream out;
756 dump_names(type, out);
757 return out.str();
758 }
759
760 void dump_names(const type_tree* x, ostream& out) {
761 if (!x) return;
762 if (x->atom) {
763 ¦ out << '"' << x->name << '"';
764 ¦ return;
765 }
766 out << '(';
767 const type_tree* curr = x;
768 while (curr && !curr->atom) {
769 ¦ dump_names(curr->left, out);
770 ¦ if (curr->right) out << ' ';
771 ¦ curr = curr->right;
772 }
773
774 if (curr) {
775 ¦ out << ". ";
776 ¦ dump_names(curr, out);
777 }
778 out << ')';
779 }
780
781 string names_to_string_without_quotes(const type_tree* type) {
782 if (type == NULL) return "()";
783 ostringstream out;
784 dump_names_without_quotes(type, out);
785 return out.str();
786 }
787
788 void dump_names_without_quotes(const type_tree* x, ostream& out) {
789 if (!x) return;
790 if (x->atom) {
791 ¦ out << x->name;
792 ¦ return;
793 }
794 out << '(';
795 const type_tree* curr = x;
796 while (curr && !curr->atom) {
797 ¦ dump_names_without_quotes(curr->left, out);
798 ¦ if (curr->right) out << ' ';
799 ¦ curr = curr->right;
800 }
801
802 if (curr) {
803 ¦ out << ". ";
804 ¦ dump_names_without_quotes(curr, out);
805 }
806 out << ')';
807 }
808
809
810
811 :(before "End Types")
812 struct no_scientific {
813 double x;
814 explicit no_scientific(double y) :x(y) {}
815 };
816
817 :(code)
818 ostream& operator<<(ostream& os, no_scientific x) {
819 if (!isfinite(x.x)) {
820 ¦
821 ¦ os << x.x;
822 ¦ return os;
823 }
824 ostringstream tmp;
825
826
827 tmp << std::fixed << x.x;
828 os << trim_floating_point(tmp.str());
829 return os;
830 }
831
832 string trim_floating_point(const string& in) {
833 if (in.empty()) return "";
834 if (in.find('.') == string::npos) return in;
835 int length = SIZE(in);
836 while (length > 1) {
837 ¦ if (in.at(length-1) != '0') break;
838 ¦ --length;
839 }
840 if (in.at(length-1) == '.') --length;
841 if (length == 0) return "0";
842 return in.substr(0, length);
843 }
844
845 void test_trim_floating_point() {
846 CHECK_EQ(trim_floating_point(""), "");
847 CHECK_EQ(trim_floating_point(".0"), "0");
848 CHECK_EQ(trim_floating_point("1.5000"), "1.5");
849 CHECK_EQ(trim_floating_point("1.000001"), "1.000001");
850 CHECK_EQ(trim_floating_point("23.000000"), "23");
851 CHECK_EQ(trim_floating_point("23.0"), "23");
852 CHECK_EQ(trim_floating_point("23."), "23");
853 CHECK_EQ(trim_floating_point("23"), "23");
854 CHECK_EQ(trim_floating_point("230"), "230");
855 CHECK_EQ(trim_floating_point("3.000000"), "3");
856 CHECK_EQ(trim_floating_point("3.0"), "3");
857 CHECK_EQ(trim_floating_point("3."), "3");
858 CHECK_EQ(trim_floating_point("3"), "3");
859 }
860
861 :(before "End Includes")
862 #include <utility>
863 using std::pair;
864 #include <math.h>