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 }