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 debug_string(const recipe& x) {
607 ostringstream out;
608 out << "- recipe " << x.name << '\n';
609
610 for (int index = 0; index < SIZE(x.steps); ++index) {
611 ¦ const instruction& inst = x.steps.at(index);
612 ¦ out << "inst: " << to_string(inst) << '\n';
613 ¦ out << " ingredients\n";
614 ¦ for (int i = 0; i < SIZE(inst.ingredients); ++i)
615 ¦ ¦ out << " " << debug_string(inst.ingredients.at(i)) << '\n';
616 ¦ out << " products\n";
617 ¦ for (int i = 0; i < SIZE(inst.products); ++i)
618 ¦ ¦ out << " " << debug_string(inst.products.at(i)) << '\n';
619 }
620 return out.str();
621 }
622
623 string to_original_string(const instruction& inst) {
624 if (inst.is_label) return inst.label;
625 ostringstream out;
626 for (int i = 0; i < SIZE(inst.products); ++i) {
627 ¦ if (i > 0) out << ", ";
628 ¦ out << inst.products.at(i).original_string;
629 }
630 if (!inst.products.empty()) out << " <- ";
631 out << inst.name;
632 if (!inst.ingredients.empty()) out << ' ';
633 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
634 ¦ if (i > 0) out << ", ";
635 ¦ out << inst.ingredients.at(i).original_string;
636 }
637 return out.str();
638 }
639
640 string to_string(const instruction& inst) {
641 if (inst.is_label) return inst.label;
642 ostringstream out;
643 for (int i = 0; i < SIZE(inst.products); ++i) {
644 ¦ if (i > 0) out << ", ";
645 ¦ out << to_string(inst.products.at(i));
646 }
647 if (!inst.products.empty()) out << " <- ";
648 out << inst.name << ' ';
649 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
650 ¦ if (i > 0) out << ", ";
651 ¦ out << to_string(inst.ingredients.at(i));
652 }
653 return out.str();
654 }
655
656 string to_string(const reagent& r) {
657 if (is_dummy(r)) return "_";
658 ostringstream out;
659 out << "{";
660 out << r.name << ": " << names_to_string(r.type);
661 if (!r.properties.empty()) {
662 ¦ for (int i = 0; i < SIZE(r.properties); ++i)
663 ¦ ¦ out << ", \"" << r.properties.at(i).first << "\": " << to_string(r.properties.at(i).second);
664 }
665 out << "}";
666 return out.str();
667 }
668
669
670 bool is_dummy(const reagent& x) {
671 return x.name == "_";
672 }
673
674 string debug_string(const reagent& x) {
675 ostringstream out;
676 out << x.name << ": " << x.value << ' ' << to_string(x.type) << " -- " << to_string(x);
677 return out.str();
678 }
679
680 string to_string(const string_tree* property) {
681 if (!property) return "()";
682 ostringstream out;
683 dump(property, out);
684 return out.str();
685 }
686
687 void dump(const string_tree* x, ostream& out) {
688 if (!x) return;
689 if (x->atom) {
690 ¦ out << '"' << x->value << '"';
691 ¦ return;
692 }
693 out << '(';
694 const string_tree* curr = x;
695 while (curr && !curr->atom) {
696 ¦ dump(curr->left, out);
697 ¦ if (curr->right) out << ' ';
698 ¦ curr = curr->right;
699 }
700
701 if (curr) {
702 ¦ out << ". ";
703 ¦ dump(curr, out);
704 }
705 out << ')';
706 }
707
708 string to_string(const type_tree* type) {
709
710 if (!type) return "NULLNULLNULL";
711 ostringstream out;
712 dump(type, out);
713 return out.str();
714 }
715
716 void dump(const type_tree* x, ostream& out) {
717 if (!x) return;
718 if (x->atom) {
719 ¦ dump(x->value, out);
720 ¦ return;
721 }
722 out << '(';
723 const type_tree* curr = x;
724 while (curr && !curr->atom) {
725 ¦ dump(curr->left, out);
726 ¦ if (curr->right) out << ' ';
727 ¦ curr = curr->right;
728 }
729
730 if (curr) {
731 ¦ out << ". ";
732 ¦ dump(curr, out);
733 }
734 out << ')';
735 }
736
737 void dump(type_ordinal type, ostream& out) {
738 if (contains_key(Type, type))
739 ¦ out << get(Type, type).name;
740 else
741 ¦ out << "?" << type;
742 }
743
744 string names_to_string(const type_tree* type) {
745
746 if (!type) return "()";
747 ostringstream out;
748 dump_names(type, out);
749 return out.str();
750 }
751
752 void dump_names(const type_tree* x, ostream& out) {
753 if (!x) return;
754 if (x->atom) {
755 ¦ out << '"' << x->name << '"';
756 ¦ return;
757 }
758 out << '(';
759 const type_tree* curr = x;
760 while (curr && !curr->atom) {
761 ¦ dump_names(curr->left, out);
762 ¦ if (curr->right) out << ' ';
763 ¦ curr = curr->right;
764 }
765
766 if (curr) {
767 ¦ out << ". ";
768 ¦ dump_names(curr, out);
769 }
770 out << ')';
771 }
772
773 string names_to_string_without_quotes(const type_tree* type) {
774
775 if (!type) return "NULLNULLNULL";
776 ostringstream out;
777 dump_names_without_quotes(type, out);
778 return out.str();
779 }
780
781 void dump_names_without_quotes(const type_tree* x, ostream& out) {
782 if (!x) return;
783 if (x->atom) {
784 ¦ out << x->name;
785 ¦ return;
786 }
787 out << '(';
788 const type_tree* curr = x;
789 while (curr && !curr->atom) {
790 ¦ dump_names_without_quotes(curr->left, out);
791 ¦ if (curr->right) out << ' ';
792 ¦ curr = curr->right;
793 }
794
795 if (curr) {
796 ¦ out << ". ";
797 ¦ dump_names_without_quotes(curr, out);
798 }
799 out << ')';
800 }
801
802
803
804 :(before "End Types")
805 struct no_scientific {
806 double x;
807 explicit no_scientific(double y) :x(y) {}
808 };
809
810 :(code)
811 ostream& operator<<(ostream& os, no_scientific x) {
812 if (!isfinite(x.x)) {
813 ¦
814 ¦ os << x.x;
815 ¦ return os;
816 }
817 ostringstream tmp;
818
819
820 tmp << std::fixed << x.x;
821 os << trim_floating_point(tmp.str());
822 return os;
823 }
824
825 string trim_floating_point(const string& in) {
826 if (in.empty()) return "";
827 if (in.find('.') == string::npos) return in;
828 int length = SIZE(in);
829 while (length > 1) {
830 ¦ if (in.at(length-1) != '0') break;
831 ¦ --length;
832 }
833 if (in.at(length-1) == '.') --length;
834 if (length == 0) return "0";
835 return in.substr(0, length);
836 }
837
838 void test_trim_floating_point() {
839 CHECK_EQ(trim_floating_point(""), "");
840 CHECK_EQ(trim_floating_point(".0"), "0");
841 CHECK_EQ(trim_floating_point("1.5000"), "1.5");
842 CHECK_EQ(trim_floating_point("1.000001"), "1.000001");
843 CHECK_EQ(trim_floating_point("23.000000"), "23");
844 CHECK_EQ(trim_floating_point("23.0"), "23");
845 CHECK_EQ(trim_floating_point("23."), "23");
846 CHECK_EQ(trim_floating_point("23"), "23");
847 CHECK_EQ(trim_floating_point("230"), "230");
848 CHECK_EQ(trim_floating_point("3.000000"), "3");
849 CHECK_EQ(trim_floating_point("3.0"), "3");
850 CHECK_EQ(trim_floating_point("3."), "3");
851 CHECK_EQ(trim_floating_point("3"), "3");
852 }
853
854 :(before "End Includes")
855 #include <utility>
856 using std::pair;
857 #include <math.h>