Provider_script::Chain Provider_polyglot::parse_until()

in mysqlshdk/shellcore/provider_polyglot.cc [172:352]


Provider_script::Chain Provider_polyglot::parse_until(const std::string &s,
                                                      size_t *pos,
                                                      int close_char,
                                                      size_t *chain_start_pos) {
  size_t end = s.length();
  size_t &p = *pos;
  Provider_script::Chain chain;
  std::string identifier;

  while (p < end && !chain.invalid()) {
    switch (s[p]) {
      case '"':
        p = mysqlshdk::utils::span_quoted_string_dq(s, p);
        if (p == std::string::npos) {
          chain.invalidate();
          return chain;
        }
        --p;
        break;

      case '\'':
        p = mysqlshdk::utils::span_quoted_string_sq(s, p);
        if (p == std::string::npos) {
          chain.invalidate();
          return chain;
        }
        --p;
        break;

      case '{':
      case '[':
      case '(': {
        int closer = 0;
        switch (s[p]) {
          case '[':
            closer = ']';
            break;
          case '{':
            closer = '}';
            break;
          case '(':
            closer = ')';
            break;
        }
        ++p;
        size_t inner_pos = p;
        Provider_script::Chain inner(parse_until(s, &p, closer, &inner_pos));
        // if the inner block was not closed, then it's the last one
        if (p == end || inner.invalid()) {
          *chain_start_pos = inner_pos;
          return inner;
        }
        // otherwise, throw it away, consider it a method call/array/map
        // and continue
        if (!identifier.empty()) {
          if (closer == '}') {
            // this was a dict
            chain.clear();
          } else if (closer == ']' && identifier.empty()) {
            // this was an array
            chain.clear();
          } else if (closer == ')') {
            if (chain.add_method(identifier))
              *chain_start_pos = p - identifier.length();
          } else {
            if (chain.add_variable(identifier))
              *chain_start_pos = p - identifier.length();
          }
          identifier.clear();
        }
        break;
      }

      case '}':
      case ')':
      case ']':
        if (s[p] == close_char) return chain;
        chain.clear();
        identifier.clear();
        // unexpected closing thingy, probably bad syntax, but we don't care
        break;

      case '\n':  // a new line is a hard break on the previous part of a stmt
      case ' ':   // or whitespace
      case '\t':
      case '\r':
        chain.clear();
        identifier.clear();
        break;

      case '/':  // comment starting
        if (p + 1 < end) {
          if (s[p + 1] == '/') {
            // skip until EOL
            size_t nl = s.find('\n', p);
            if (nl == std::string::npos) {
              chain.clear();
              identifier.clear();
              p = end;
            } else {
              p = nl;
            }
          } else if (s[p + 1] == '*') {
            size_t eoc = mysqlshdk::utils::span_cstyle_comment(s, p);
            if (eoc == std::string::npos) {
              chain.invalidate();
              return chain;
            } else {
              p = eoc;
            }
          }
        }
        break;

      case '\\': {  // escape outside a string means line continuation..
                    // skip util end of line
        size_t nl = s.find('\n', p);
        if (nl == std::string::npos) {
          chain.clear();
          identifier.clear();
          p = end;
        } else {
          p = nl;
        }
        break;
      }

      case '.':
        if (!identifier.empty()) {
          if (chain.add_variable(identifier))
            *chain_start_pos = p - identifier.length();
        } else {
          // consecutive dots or dot at beginning = syntax error
          if (p == 0 || chain.empty() || s[p - 1] == '.') {
            chain.invalidate();
            return chain;
          }
        }
        identifier.clear();
        chain.add_dot();
        break;

      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        if (!identifier.empty()) {
          identifier.push_back(s[p]);
        } else {  // a number can't appear in a chain, outside an id or param
          chain.clear();
        }
        break;

      case '_':
        identifier.push_back(s[p]);
        break;

      default:
        // if we see any identifier-like char, we're in an identifier
        // anything else is a break
        if (isalpha(s[p])) {
          identifier.push_back(s[p]);
        } else {
          // garbage...
          chain.clear();
          identifier.clear();
        }
        break;
    }
    ++p;
  }
  if (chain.add_variable(identifier))
    *chain_start_pos = p - identifier.length();
  return chain;
}