1 //: Beginning of level 3: support for automatically aggregating functions into
  2 //: test suites.
  3 //:
  4 //: (As explained in the transform layer, level 3 runs before level 2. We
  5 //: can't use any of the transforms in previous layers. But we *do* rely on
  6 //: those concepts being present in the input. Particularly labels.)
  7 
  8 :(after "Begin Transforms")
  9 // Begin Level-3 Transforms
 10 Transform.push_back(create_test_function);
 11 // End Level-3 Transforms
 12 
 13 :(scenario run_test)
 14 % Reg[ESP].u = 0x100;
 15 == 0x1
 16 main:
 17   e8/call run_tests/disp32  # 5 bytes
 18   f4/halt                   # 1 byte
 19 
 20 test_foo:  # offset 7
 21   01 d8  # just some unique instruction: add EBX to EAX
 22   c3/return
 23 
 24 # check that code in test_foo ran (implicitly called by run_tests)
 25 +run: inst: 0x00000007
 26 
 27 :(code)
 28 void create_test_function(program& p) {
 29   if (p.segments.empty()) return;
 30   segment& code = p.segments.at(0);
 31   trace(99, "transform") << "-- create 'run_tests'" << end();
 32   vector<line> new_insts;
 33   for (int i = 0;  i < SIZE(code.lines);  ++i) {
 34     line& inst = code.lines.at(i);
 35     for (int j = 0;  j < SIZE(inst.words);  ++j) {
 36       const word& curr = inst.words.at(j);
 37       if (*curr.data.rbegin() != ':') continue;  // not a label
 38       if (!starts_with(curr.data, "test_")) continue;
 39       string fn = drop_last(curr.data);
 40       new_insts.push_back(call(fn));
 41     }
 42   }
 43   if (new_insts.empty()) return;  // no tests found
 44   code.lines.push_back(label("run_tests"));
 45   code.lines.insert(code.lines.end(), new_insts.begin(), new_insts.end());
 46   code.lines.push_back(ret());
 47 }
 48 
 49 string to_string(const segment& s) {
 50   ostringstream out;
 51   for (int i = 0;  i < SIZE(s.lines);  ++i) {
 52     const line& l = s.lines.at(i);
 53     for (int j = 0;  j < SIZE(l.words);  ++j) {
 54       if (j > 0) out << ' ';
 55       out << to_string(l.words.at(j));
 56     }
 57     out << '\n';
 58   }
 59   return out.str();
 60 }
 61 
 62 string to_string(const word& w) {
 63   ostringstream out;
 64   out << w.data;
 65   for (int i = 0;  i < SIZE(w.metadata);  ++i)
 66     out << '/' << w.metadata.at(i);
 67   return out.str();
 68 }
 69 
 70 line label(string s) {
 71   line result;
 72   result.words.push_back(word());
 73   result.words.back().data = (s+":");
 74   return result;
 75 }
 76 
 77 line call(string s) {
 78   line result;
 79   result.words.push_back(call());
 80   result.words.push_back(disp32(s));
 81   return result;
 82 }
 83 
 84 word call() {
 85   word result;
 86   result.data = "e8";
 87   result.metadata.push_back("call");
 88   return result;
 89 }
 90 
 91 word disp32(string s) {
 92   word result;
 93   result.data = s;
 94   result.metadata.push_back("disp32");
 95   return result;
 96 }
 97 
 98 line ret() {
 99   line result;
100   result.words.push_back(word());
101   result.words.back().data = "c3";
102   result.words.back().metadata.push_back("return");
103   return result;
104 }