https://github.com/akkartik/mu/blob/master/016dilated_reagent.cc
  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 }