1 //: An alternative syntax for reagents that permits whitespace in properties,
  2 //: grouped by brackets. We'll use this ability in the next layer, when we
  3 //: generalize types from lists to trees of properties.
  4 
  5 :(scenarios load)
  6 :(scenario dilated_reagent)
  7 def main [
  8   {1: number, foo: bar} <- copy 34
  9 ]
 10 +parse:   product: {1: "number", "foo": "bar"}
 11 
 12 :(scenario load_trailing_space_after_curly_bracket)
 13 def main [
 14   # line below has a space at the end
 15   { 
 16 ]
 17 # successfully parsed
 18 
 19 :(scenario dilated_reagent_with_comment)
 20 def main [
 21   {1: number, foo: bar} <- copy 34  # test comment
 22 ]
 23 +parse:   product: {1: "number", "foo": "bar"}
 24 $error: 0
 25 
 26 :(scenario dilated_reagent_with_comment_immediately_following)
 27 def main [
 28   1:number <- copy {34: literal}  # test comment
 29 ]
 30 $error: 0
 31 
 32 //: First augment next_word to group balanced brackets together.
 33 
 34 :(before "End next_word Special-cases")
 35 if (in.peek() == '(')
 36   return slurp_balanced_bracket(in);
 37 // treat curlies mostly like parens, but don't mess up labels
 38 if (start_of_dilated_reagent(in))
 39   return slurp_balanced_bracket(in);
 40 
 41 :(code)
 42 // A curly is considered a label if it's the last thing on a line. Dilated
 43 // reagents should remain all on one line.
 44 bool start_of_dilated_reagent(istream& in) {
 45   if (in.peek() != '{') return false;
 46   int pos = in.tellg();
 47   in.get();  // slurp '{'
 48   skip_whitespace_but_not_newline(in);
 49   char next = in.peek();
 50   in.seekg(pos);
 51   return next != '\n';
 52 }
 53 
 54 // Assume the first letter is an open bracket, and read everything until the
 55 // matching close bracket.
 56 // We balance {} () and [].
 57 string slurp_balanced_bracket(istream& in) {
 58   ostringstream result;
 59   char c;
 60   list<char> open_brackets;
 61   while (in >> c) {
 62   ¦ if (c == '(') open_brackets.push_back(c);
 63   ¦ if (c == ')') {
 64   ¦ ¦ if (open_brackets.empty() || open_brackets.back() != '(') {
 65   ¦ ¦ ¦ raise << "unbalanced ')'\n" << end();
 66   ¦ ¦ ¦ continue;
 67   ¦ ¦ }
 68   ¦ ¦ assert(open_brackets.back() == '(');
 69   ¦ ¦ open_brackets.pop_back();
 70   ¦ }
 71   ¦ if (c == '[') open_brackets.push_back(c);
 72   ¦ if (c == ']') {
 73   ¦ ¦ if (open_brackets.empty() || open_brackets.back() != '[') {
 74   ¦ ¦ ¦ raise << "unbalanced ']'\n" << end();
 75   ¦ ¦ ¦ continue;
 76   ¦ ¦ }
 77   ¦ ¦ open_brackets.pop_back();
 78   ¦ }
 79   ¦ if (c == '{') open_brackets.push_back(c);
 80   ¦ if (c == '}') {
 81   ¦ ¦ if (open_brackets.empty() || open_brackets.back() != '{') {
 82   ¦ ¦ ¦ raise << "unbalanced '}'\n" << end();
 83   ¦ ¦ ¦ continue;
 84   ¦ ¦ }
 85   ¦ ¦ open_brackets.pop_back();
 86   ¦ }
 87   ¦ result << c;
 88   ¦ if (open_brackets.empty()) break;
 89   }
 90   skip_whitespace_and_comments_but_not_newline(in);
 91   return result.str();
 92 }
 93 
 94 :(after "Parsing reagent(string s)")
 95 if (starts_with(s, "{")) {
 96   assert(properties.empty());
 97   istringstream in(s);
 98   in >> std::noskipws;
 99   in.get();  // skip '{'
100   name = slurp_key(in);
101   if (name.empty()) {
102   ¦ raise << "invalid reagent '" << s << "' without a name\n" << end();
103   ¦ return;
104   }
105   if (name == "}") {
106   ¦ raise << "invalid empty reagent '" << s << "'\n" << end();
107   ¦ return;
108   }
109   {
110   ¦ string s = next_word(in);
111   ¦ if (s.empty()) {
112   ¦ ¦ assert(!has_data(in));
113   ¦ ¦ raise << "incomplete dilated reagent at end of file (0)\n" << end();
114   ¦ ¦ return;
115   ¦ }
116   ¦ string_tree* type_names = new string_tree(s);
117   ¦ // End Parsing Dilated Reagent Type Property(type_names)
118   ¦ type = new_type_tree(type_names);
119   ¦ delete type_names;
120   }
121   while (has_data(in)) {
122   ¦ string key = slurp_key(in);
123   ¦ if (key.empty()) continue;
124   ¦ if (key == "}") continue;
125   ¦ string s = next_word(in);
126   ¦ if (s.empty()) {
127   ¦ ¦ assert(!has_data(in));
128   ¦ ¦ raise << "incomplete dilated reagent at end of file (1)\n" << end();
129   ¦ ¦ return;
130   ¦ }
131   ¦ string_tree* value = new string_tree(s);
132   ¦ // End Parsing Dilated Reagent Property(value)
133   ¦ properties.push_back(pair<string, string_tree*>(key, value));
134   }
135   return;
136 }
137 
138 :(code)
139 string slurp_key(istream& in) {
140   string result = next_word(in);
141   if (result.empty()) {
142   ¦ assert(!has_data(in));
143   ¦ raise << "incomplete dilated reagent at end of file (2)\n" << end();
144   ¦ return result;
145   }
146   while (!result.empty() && *result.rbegin() == ':')
147   ¦ strip_last(result);
148   while (isspace(in.peek()) || in.peek() == ':')
149   ¦ in.get();
150   return result;
151 }