about summary refs log blame commit diff stats
path: root/cpp/050scenario
blob: 8a4cf86956432b9fe8deba7dd1c3331b91b422cf (plain) (tree)
1
2
3
4
5
6
7
8
9
                                          
 


                     
                    

                                    
                        




                           
                            
 
                     

                                          


                                               

       

















                                                                                                                                                

     

                          

 
                             

                             
                                             









                                 
                                                       











                                                   









                                          













                                                               



                                                 



                                          

                                    
                                                                             








                                                                   


                                                                




                                                                       
                                                                                  

                                   
                                                                                          
   
                                                                                                     




                                                 





                                






                                                             

                                                                                                                                       






                                                                            
























                                                                                      

            

                                                              

                   

                                



                                                   
 
       























                                                                                                          







                                              
//: Allow tests to be written in mu files.

:(before "End Types")
struct scenario {
  string name;
  string dump_layer;
  string to_run;
  map<int, int> memory_expectations;
  // End scenario Fields
};

:(before "End Globals")
vector<scenario> Scenarios;

//:: How we check Scenarios.

:(before "End Tests")
time_t mu_time; time(&mu_time);
cerr << "\nMu tests: " << ctime(&mu_time);
for (size_t i = 0; i < Scenarios.size(); ++i) {
  run_mu_test(i);
}

:(code)
void run_mu_test(size_t i) {
  setup();
  Trace_file = Scenarios[i].name;
  START_TRACING_UNTIL_END_OF_SCOPE
  if (!Scenarios[i].dump_layer.empty())
    Trace_stream->dump_layer = Scenarios[i].dump_layer;
//?   cerr << "AAA " << Scenarios[i].name << '\n'; //? 1
//?   cout << Scenarios[i].to_run; //? 2
  run(Scenarios[i].to_run);
//?   cout << "after: " << Memory[1] << '\n'; //? 1
//?   cout << "after:\n";  dump_memory(); //? 1
  for (map<int, int>::iterator p = Scenarios[i].memory_expectations.begin();
       p != Scenarios[i].memory_expectations.end();
       ++p) {
    if (Memory[p->first] != p->second) {
      // todo: unit tests for the test parsing infrastructure; use raise?
      cerr << Scenarios[i].name << ": Expected location " << p->first << " to contain " << p->second << " but saw " << Memory[p->first] << '\n';
      Passed = false;
    }
  }
  // End Scenario Checks
  if (Passed) cerr << ".";
}

//:: How we create Scenarios.

:(scenarios "parse_scenario")
:(scenario parse_scenario_memory_expectation)
scenario foo [
  run [
    a <- b
  ]
  memory should contain [
    1 <- 0
  ]
]
+parse: scenario will run: a <- b

:(scenario parse_scenario_memory_expectation_duplicate)
hide warnings
scenario foo [
  run [
    a <- b
  ]
  memory should contain [
    1 <- 0
    1 <- 1
  ]
]
+warn: duplicate expectation for location 1: 0 -> 1

:(before "End Command Handlers")
else if (command == "scenario") {
//?   cout << "AAA scenario\n"; //? 1
  Scenarios.push_back(parse_scenario(in));
}

:(code)
scenario parse_scenario(istream& in) {
  scenario x;
  x.name = next_word(in);
  trace("parse") << "reading scenario " << x.name;
  skip_bracket(in, "'scenario' must begin with '['");
  ostringstream buffer;
  slurp_until_matching_bracket(in, buffer);
//?   cout << "inner buffer: ^" << buffer.str() << "$\n"; //? 1
  istringstream inner(buffer.str());
  inner >> std::noskipws;
  while (!inner.eof()) {
    skip_whitespace_and_comments(inner);
    string scenario_command = next_word(inner);
    if (scenario_command.empty() && inner.eof()) break;
    // Scenario Command Handlers
    if (scenario_command == "run") {
      handle_scenario_run_directive(inner, x);
    }
    else if (scenario_command == "memory") {
      handle_scenario_memory_directive(inner, x);
    }
    else if (scenario_command == "dump") {
      skip_whitespace_and_comments(inner);
      x.dump_layer = next_word(inner);
    }
    // End Scenario Command Handlers
    else {
      raise << "unknown command in scenario: ^" << scenario_command << "$\n";
    }
  }
  return x;
}

void handle_scenario_run_directive(istream& in, scenario& result) {
  skip_bracket(in, "'run' inside scenario must begin with '['");
  ostringstream buffer;
  slurp_until_matching_bracket(in, buffer);
  string trace_result = buffer.str();  // temporary copy
  trace("parse") << "scenario will run: " << trim(trace_result);
//?   cout << buffer.str() << '\n'; //? 1
  result.to_run = "recipe test-"+result.name+" [" + buffer.str() + "]";
}

void handle_scenario_memory_directive(istream& in, scenario& out) {
  if (next_word(in) != "should") {
    raise << "'memory' directive inside scenario must continue 'memory should'\n";
  }
  if (next_word(in) != "contain") {
    raise << "'memory' directive inside scenario must continue 'memory should contain'\n";
  }
  skip_bracket(in, "'memory' directive inside scenario must begin with 'memory should contain ['\n");
  while (true) {
    skip_whitespace_and_comments(in);
    if (in.eof()) break;
//?     cout << "a: " << in.peek() << '\n'; //? 1
    if (in.peek() == ']') break;
    string lhs = next_word(in);
    if (!is_number(lhs)) {
      handle_type(lhs, in, out);
      continue;
    }
    int address = to_int(lhs);
//?     cout << "address: " << address << '\n'; //? 2
//?     cout << "b: " << in.peek() << '\n'; //? 1
    skip_whitespace_and_comments(in);
//?     cout << "c: " << in.peek() << '\n'; //? 1
    string _assign;  in >> _assign;  assert(_assign == "<-");
    skip_whitespace_and_comments(in);
    int value = 0;  in >> value;
    if (out.memory_expectations.find(address) != out.memory_expectations.end())
      raise << "duplicate expectation for location " << address << ": " << out.memory_expectations[address] << " -> " << value << '\n';
    out.memory_expectations[address] = value;
    trace("parse") << "memory expectation: *" << address << " == " << value;
  }
  skip_whitespace(in);
  assert(in.get() == ']');
}

void handle_type(const string& lhs, istream& in, scenario& out) {
  reagent x(lhs);
  if (x.properties[0].second[0] == "string") {
    x.set_value(to_int(x.name));
//?     cerr << x.name << ' ' << x.value << '\n'; //? 1
    skip_whitespace_and_comments(in);
    string _assign = next_word(in);
//?     cerr << _assign << '\n'; //? 1
    assert(_assign == "<-");
    skip_whitespace_and_comments(in);
    string literal = next_word(in);
//?     cerr << literal << '\n'; //? 1
    size_t address = x.value;
    out.memory_expectations[address] = literal.size()-2;  // exclude quoting brackets
    ++address;
    for (size_t i = 1; i < literal.size()-1; ++i) {
//?       cerr << "checking " << address << ": " << literal[i] << '\n'; //? 1
      out.memory_expectations[address] = literal[i];
      ++address;
    }
    return;
  }
  raise << "scenario doesn't know how to parse memory expectation on " << lhs << '\n';
}

//:: Helpers

void slurp_until_matching_bracket(istream& in, ostream& out) {
  int brace_depth = 1;  // just scanned '['
  char c;
  while (in >> c) {
    if (c == '[') ++brace_depth;
    if (c == ']') --brace_depth;
    if (brace_depth == 0) break;  // drop final ']'
    out << c;
  }
}

:(code)
// for tests
void parse_scenario(const string& s) {
  istringstream in(s);
  in >> std::noskipws;
  skip_whitespace_and_comments(in);
  string _scenario = next_word(in);
//?   cout << _scenario << '\n'; //? 1
  assert(_scenario == "scenario");
  parse_scenario(in);
}

string &trim(string &s) {
  return ltrim(rtrim(s));
}

string &ltrim(string &s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(isspace))));
  return s;
}

string &rtrim(string &s) {
  s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(isspace))).base(), s.end());
  return s;
}

:(before "End Includes")
#include <sys/stat.h>
:(code)
bool file_exists(const string& filename) {
  struct stat buffer;
  return stat(filename.c_str(), &buffer) == 0;
}
">2; Type[tmp].kind = exclusive_container; Type[tmp].name = "number-or-point"; vector<type_ordinal> t1; t1.push_back(number); Type[tmp].elements.push_back(t1); vector<type_ordinal> t2; t2.push_back(point); Type[tmp].elements.push_back(t2); Type[tmp].element_names.push_back("i"); Type[tmp].element_names.push_back("p"); } //: Tests in this layer often explicitly setup memory before reading it as an //: array. Don't do this in general. I'm tagging exceptions with /raw to //: avoid warnings. :(scenario copy_exclusive_container) # Copying exclusive containers copies all their contents and an extra location for the tag. recipe main [ 1:number <- copy 1 # 'point' variant 2:number <- copy 34 3:number <- copy 35 4:number-or-point <- copy 1:number-or-point/raw # unsafe ] +mem: storing 1 in location 4 +mem: storing 34 in location 5 +mem: storing 35 in location 6 :(before "End size_of(types) Cases") if (t.kind == exclusive_container) { // size of an exclusive container is the size of its largest variant // (So like containers, it can't contain arrays.) long long int result = 0; for (long long int i = 0; i < t.size; ++i) { long long int tmp = size_of(t.elements.at(i)); if (tmp > result) result = tmp; } // ...+1 for its tag. return result+1; } //:: To access variants of an exclusive container, use 'maybe-convert'. //: It always returns an address (so that you can modify it) or null (to //: signal that the conversion failed (because the container contains a //: different variant). //: 'maybe-convert' requires a literal in ingredient 1. We'll use a synonym //: called 'variant'. :(before "End Mu Types Initialization") Type_ordinal["variant"] = 0; :(scenario maybe_convert) recipe main [ 12:number <- copy 1 13:number <- copy 35 14:number <- copy 36 20:address:point <- maybe-convert 12:number-or-point/raw, 1:variant # unsafe ] +mem: storing 13 in location 20 :(scenario maybe_convert_fail) recipe main [ 12:number <- copy 1 13:number <- copy 35 14:number <- copy 36 20:address:point <- maybe-convert 12:number-or-point/raw, 0:variant # unsafe ] +mem: storing 0 in location 20 :(before "End Primitive Recipe Declarations") MAYBE_CONVERT, :(before "End Primitive Recipe Numbers") Recipe_ordinal["maybe-convert"] = MAYBE_CONVERT; :(before "End Primitive Recipe Implementations") case MAYBE_CONVERT: { if (SIZE(ingredients) != 2) { raise << current_recipe_name() << ": 'maybe-convert' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end(); break; } reagent base = canonize(current_instruction().ingredients.at(0)); long long int base_address = base.value; if (base_address == 0) { raise << current_recipe_name() << ": tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end(); break; } if (base.types.empty() || Type[base.types.at(0)].kind != exclusive_container) { raise << current_recipe_name () << ": first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end(); break; } if (!is_literal(current_instruction().ingredients.at(1))) { raise << current_recipe_name() << ": second ingredient of 'maybe-convert' should have type 'variant', but got " << current_instruction().ingredients.at(1).original_string << '\n' << end(); break; } long long int tag = current_instruction().ingredients.at(1).value; long long int result; if (tag == static_cast<long long int>(Memory[base_address])) { result = base_address+1; } else { result = 0; } products.resize(1); products.at(0).push_back(result); break; } //:: Allow exclusive containers to be defined in mu code. :(scenario exclusive_container) exclusive-container foo [ x:number y:number ] +parse: reading exclusive-container foo +parse: element name: x +parse: type: 1 +parse: element name: y +parse: type: 1 :(before "End Command Handlers") else if (command == "exclusive-container") { insert_container(command, exclusive_container, in); } //:: To construct exclusive containers out of variant types, use 'merge'. :(scenario lift_to_exclusive_container) exclusive-container foo [ x:number y:number ] recipe main [ 1:number <- copy 34 2:foo <- merge 0/x, 1:number 4:foo <- merge 1/x, 1:number ] +mem: storing 0 in location 2 +mem: storing 34 in location 3 +mem: storing 1 in location 4 +mem: storing 34 in location 5 //: Since the different variants of an exclusive-container might have //: different sizes, relax the size mismatch check for 'merge' instructions. :(before "End size_mismatch(x) Cases") if (current_instruction().operation == MERGE && !current_instruction().products.empty() && !current_instruction().products.at(0).types.empty()) { reagent x = canonize(current_instruction().products.at(0)); if (Type[x.types.at(0)].kind == exclusive_container) { return size_of(x) < SIZE(data); } } :(scenario merge_exclusive_container_with_mismatched_sizes) container foo [ x:number y:number ] exclusive-container bar [ x:number y:foo ] recipe main [ 1:number <- copy 34 2:number <- copy 35 3:bar <- merge 0/x, 1:number 6:bar <- merge 1/foo, 1:number, 2:number ] +mem: storing 0 in location 3 +mem: storing 34 in location 4 # bar is always 3 large so location 5 is skipped +mem: storing 1 in location 6 +mem: storing 34 in location 7 +mem: storing 35 in location 8