3539
Always check if next_word() returned an empty string (if it hit eof). Thanks Rebecca Allard for running into a crash when a .mu file ends with '{' (without a following newline). Open question: how to express the constraint that next_word() should always check if its result is empty? Can *any* type system do that?! Even the usual constraint that we must use a result isn't iron-clad: you could save the result in a variable but then ignore it. Unless you go to Go's extraordinary lengths of considering any dead code an error.
This commit is contained in:
parent
22d93b7671
commit
66abe7c1bd
21
011load.cc
21
011load.cc
|
@ -26,6 +26,10 @@ vector<recipe_ordinal> load(istream& in) {
|
|||
skip_whitespace_and_comments(in);
|
||||
if (!has_data(in)) break;
|
||||
string command = next_word(in);
|
||||
if (command.empty()) {
|
||||
assert(!has_data(in));
|
||||
break;
|
||||
}
|
||||
// Command Handlers
|
||||
if (command == "recipe" || command == "def") {
|
||||
recipe_ordinal r = slurp_recipe(in);
|
||||
|
@ -50,6 +54,11 @@ vector<recipe_ordinal> load(istream& in) {
|
|||
int slurp_recipe(istream& in) {
|
||||
recipe result;
|
||||
result.name = next_word(in);
|
||||
if (result.name.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "file ended with 'recipe'\n" << end();
|
||||
return -1;
|
||||
}
|
||||
// End Load Recipe Name
|
||||
skip_whitespace_but_not_newline(in);
|
||||
// End Recipe Refinements
|
||||
|
@ -89,7 +98,7 @@ bool next_instruction(istream& in, instruction* curr) {
|
|||
curr->clear();
|
||||
skip_whitespace_and_comments(in);
|
||||
if (!has_data(in)) {
|
||||
raise << "0: unbalanced '[' for recipe\n" << end();
|
||||
raise << "incomplete recipe at end of file (0)\n" << end();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -97,10 +106,15 @@ bool next_instruction(istream& in, instruction* curr) {
|
|||
while (has_data(in) && in.peek() != '\n') {
|
||||
skip_whitespace_but_not_newline(in);
|
||||
if (!has_data(in)) {
|
||||
raise << "1: unbalanced '[' for recipe\n" << end();
|
||||
raise << "incomplete recipe at end of file (1)\n" << end();
|
||||
return false;
|
||||
}
|
||||
string word = next_word(in);
|
||||
if (word.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete recipe at end of file (2)\n" << end();
|
||||
return false;
|
||||
}
|
||||
words.push_back(word);
|
||||
skip_whitespace_but_not_newline(in);
|
||||
}
|
||||
|
@ -113,7 +127,7 @@ bool next_instruction(istream& in, instruction* curr) {
|
|||
curr->label = words.at(0);
|
||||
trace(9993, "parse") << "label: " << curr->label << end();
|
||||
if (!has_data(in)) {
|
||||
raise << "7: unbalanced '[' for recipe\n" << end();
|
||||
raise << "incomplete recipe at end of file (3)\n" << end();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -149,6 +163,7 @@ bool next_instruction(istream& in, instruction* curr) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// can return empty string -- only if `in` has no more data
|
||||
string next_word(istream& in) {
|
||||
skip_whitespace_but_not_newline(in);
|
||||
// End next_word Special-cases
|
||||
|
|
|
@ -107,7 +107,13 @@ if (starts_with(s, "{")) {
|
|||
return;
|
||||
}
|
||||
{
|
||||
string_tree* type_names = new string_tree(next_word(in));
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete dilated reagent at end of file (0)\n" << end();
|
||||
return;
|
||||
}
|
||||
string_tree* type_names = new string_tree(s);
|
||||
// End Parsing Dilated Reagent Type Property(type_names)
|
||||
type = new_type_tree(type_names);
|
||||
delete type_names;
|
||||
|
@ -116,7 +122,13 @@ if (starts_with(s, "{")) {
|
|||
string key = slurp_key(in);
|
||||
if (key.empty()) continue;
|
||||
if (key == "}") continue;
|
||||
string_tree* value = new string_tree(next_word(in));
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete dilated reagent at end of file (1)\n" << end();
|
||||
return;
|
||||
}
|
||||
string_tree* value = new string_tree(s);
|
||||
// End Parsing Dilated Reagent Property(value)
|
||||
properties.push_back(pair<string, string_tree*>(key, value));
|
||||
}
|
||||
|
@ -126,6 +138,11 @@ if (starts_with(s, "{")) {
|
|||
:(code)
|
||||
string slurp_key(istream& in) {
|
||||
string result = next_word(in);
|
||||
if (result.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete dilated reagent at end of file (2)\n" << end();
|
||||
return result;
|
||||
}
|
||||
while (!result.empty() && *result.rbegin() == ':')
|
||||
strip_last(result);
|
||||
while (isspace(in.peek()) || in.peek() == ':')
|
||||
|
|
|
@ -46,7 +46,13 @@ string_tree* parse_string_tree(istream& in) {
|
|||
return NULL;
|
||||
}
|
||||
if (in.peek() != '(') {
|
||||
string_tree* result = new string_tree(next_word(in));
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete string tree at end of file (0)\n" << end();
|
||||
return NULL;
|
||||
}
|
||||
string_tree* result = new string_tree(s);
|
||||
return result;
|
||||
}
|
||||
in.get(); // skip '('
|
||||
|
@ -57,10 +63,18 @@ string_tree* parse_string_tree(istream& in) {
|
|||
assert(has_data(in));
|
||||
if (in.peek() == ')') break;
|
||||
*curr = new string_tree(NULL, NULL);
|
||||
if (in.peek() == '(')
|
||||
if (in.peek() == '(') {
|
||||
(*curr)->left = parse_string_tree(in);
|
||||
else
|
||||
(*curr)->left = new string_tree(next_word(in));
|
||||
}
|
||||
else {
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete string tree at end of file (1)\n" << end();
|
||||
return NULL;
|
||||
}
|
||||
(*curr)->left = new string_tree(s);
|
||||
}
|
||||
curr = &(*curr)->right;
|
||||
}
|
||||
in.get(); // skip ')'
|
||||
|
|
|
@ -721,6 +721,11 @@ Num_calls_to_transform_all_at_first_definition = -1;
|
|||
void insert_container(const string& command, kind_of_type kind, istream& in) {
|
||||
skip_whitespace_but_not_newline(in);
|
||||
string name = next_word(in);
|
||||
if (name.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete container definition at end of file (0)\n" << end();
|
||||
return;
|
||||
}
|
||||
// End container Name Refinements
|
||||
trace(9991, "parse") << "--- defining " << command << ' ' << name << end();
|
||||
if (!contains_key(Type_ordinal, name)
|
||||
|
@ -744,6 +749,11 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
|
|||
while (has_data(in)) {
|
||||
skip_whitespace_and_comments(in);
|
||||
string element = next_word(in);
|
||||
if (element.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete container definition at end of file (1)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (element == "]") break;
|
||||
if (in.peek() != '\n') {
|
||||
raise << command << " '" << name << "' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code.\n" << end();
|
||||
|
|
|
@ -78,6 +78,11 @@ else if (command == "pending-scenario") {
|
|||
scenario parse_scenario(istream& in) {
|
||||
scenario result;
|
||||
result.name = next_word(in);
|
||||
if (result.name.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete scenario at end of file\n" << end();
|
||||
return result;
|
||||
}
|
||||
skip_whitespace_and_comments(in);
|
||||
if (in.peek() != '[') {
|
||||
raise << "Expected '[' after scenario '" << result.name << "'\n" << end();
|
||||
|
@ -329,6 +334,11 @@ void check_memory(const string& s) {
|
|||
skip_whitespace_and_comments(in);
|
||||
if (!has_data(in)) break;
|
||||
string lhs = next_word(in);
|
||||
if (lhs.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'memory-should-contain' block at end of file (0)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (!is_integer(lhs)) {
|
||||
check_type(lhs, in);
|
||||
continue;
|
||||
|
@ -338,6 +348,11 @@ void check_memory(const string& s) {
|
|||
string _assign; in >> _assign; assert(_assign == "<-");
|
||||
skip_whitespace_and_comments(in);
|
||||
string rhs = next_word(in);
|
||||
if (rhs.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'memory-should-contain' block at end of file (1)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (!is_integer(rhs) && !is_noninteger(rhs)) {
|
||||
if (Current_scenario && !Scenario_testing_scenario)
|
||||
// genuine test in a mu file
|
||||
|
@ -374,9 +389,19 @@ void check_type(const string& lhs, istream& in) {
|
|||
x.set_value(to_integer(x.name));
|
||||
skip_whitespace_and_comments(in);
|
||||
string _assign = next_word(in);
|
||||
if (_assign.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'memory-should-contain' block at end of file (2)\n" << end();
|
||||
return;
|
||||
}
|
||||
assert(_assign == "<-");
|
||||
skip_whitespace_and_comments(in);
|
||||
string literal = next_word(in);
|
||||
if (literal.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'memory-should-contain' block at end of file (3)\n" << end();
|
||||
return;
|
||||
}
|
||||
int address = x.value;
|
||||
// exclude quoting brackets
|
||||
assert(*literal.begin() == '['); literal.erase(literal.begin());
|
||||
|
@ -750,6 +775,11 @@ void run_mu_scenario(const string& form) {
|
|||
in >> std::noskipws;
|
||||
skip_whitespace_and_comments(in);
|
||||
string _scenario = next_word(in);
|
||||
if (_scenario.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "no scenario in string passed into run_mu_scenario()\n" << end();
|
||||
return;
|
||||
}
|
||||
assert(_scenario == "scenario");
|
||||
scenario s = parse_scenario(in);
|
||||
run_mu_scenario(s);
|
||||
|
|
10
052tangle.cc
10
052tangle.cc
|
@ -34,6 +34,11 @@ Fragments_used.clear();
|
|||
:(before "End Command Handlers")
|
||||
else if (command == "before") {
|
||||
string label = next_word(in);
|
||||
if (label.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'before' block at end of file\n" << end();
|
||||
return result;
|
||||
}
|
||||
recipe tmp;
|
||||
slurp_body(in, tmp);
|
||||
if (is_waypoint(label))
|
||||
|
@ -44,6 +49,11 @@ else if (command == "before") {
|
|||
}
|
||||
else if (command == "after") {
|
||||
string label = next_word(in);
|
||||
if (label.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'after' block at end of file\n" << end();
|
||||
return result;
|
||||
}
|
||||
recipe tmp;
|
||||
slurp_body(in, tmp);
|
||||
if (is_waypoint(label))
|
||||
|
|
|
@ -33,6 +33,11 @@ void load_recipe_header(istream& in, recipe& result) {
|
|||
result.has_header = true;
|
||||
while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete recipe header at end of file (0)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (s == "<-")
|
||||
raise << "recipe " << result.name << " should say '->' and not '<-'\n" << end();
|
||||
if (s == "->") break;
|
||||
|
@ -42,6 +47,11 @@ void load_recipe_header(istream& in, recipe& result) {
|
|||
}
|
||||
while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
|
||||
string s = next_word(in);
|
||||
if (s.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete recipe header at end of file (1)\n" << end();
|
||||
return;
|
||||
}
|
||||
result.products.push_back(reagent(s));
|
||||
trace(9999, "parse") << "header product: " << result.products.back().original_string << end();
|
||||
skip_whitespace_but_not_newline(in);
|
||||
|
|
|
@ -115,6 +115,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
|
|||
skip_whitespace_and_comments(in);
|
||||
if (!has_data(in)) break;
|
||||
string filename = next_word(in);
|
||||
if (filename.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'resources' block at end of file (0)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (*filename.begin() != '[') {
|
||||
raise << caller << ": assume-resources: filename '" << filename << "' must begin with a '['\n" << end();
|
||||
break;
|
||||
|
@ -130,6 +135,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
|
|||
break;
|
||||
}
|
||||
string arrow = next_word(in);
|
||||
if (arrow.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'resources' block at end of file (1)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (arrow != "<-") {
|
||||
raise << caller << ": assume-resources: expected '<-' after filename '" << filename << "' but got '" << arrow << "'\n" << end();
|
||||
break;
|
||||
|
@ -139,6 +149,11 @@ void parse_resources(const string& data, map<string, string>& out, const string&
|
|||
break;
|
||||
}
|
||||
string contents = next_word(in);
|
||||
if (contents.empty()) {
|
||||
assert(!has_data(in));
|
||||
raise << "incomplete 'resources' block at end of file (2)\n" << end();
|
||||
return;
|
||||
}
|
||||
if (*contents.begin() != '[') {
|
||||
raise << caller << ": assume-resources: file contents '" << contents << "' for filename '" << filename << "' must begin with a '['\n" << end();
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue