code: function createModel(string) {
      const reVariable = /\$\{([a-z][\w\-]*)\}/ig;
      const escapeCharCode = 92; // `\` symbol
      const variables = [];
  
      // We have to replace unescaped (e.g. not preceded with `\`) tokens.
      // Instead of writing a stream parser, we’ll cut some edges here:
      // 1. Find all tokens
      // 2. Walk string char-by-char and resolve only tokens that are not escaped
      const tokens = new Map();
      let m;
      while (m = reVariable.exec(string)) {
          tokens.set(m.index, m);
      }
  
      if (tokens.size) {
          let start = 0, pos = 0, len = string.length;
          let output = '';
          while (pos < len) {
              if (string.charCodeAt(pos) === escapeCharCode && tokens.has(pos + 1)) {
                  // Found escape symbol that escapes variable: we should
                  // omit this symbol in output string and skip variable
                  const token = tokens.get(pos + 1);
                  output += string.slice(start, pos) + token[0];
                  start = pos = token.index + token[0].length;
                  tokens.delete(pos + 1);
                  continue;
              }
  
              pos++;
          }
  
          string = output + string.slice(start);
  
          // Not using `.map()` here to reduce memory allocations
          const validMatches = Array.from(tokens.values());
          for (let i = 0, il = validMatches.length; i < il; i++) {
              const token = validMatches[i];
              variables.push({
                  name: token[1],
                  location: token.index,
                  length: token[0].length
              });
          }
      }
  
      return {string, variables};
  }
docs: Creates variable model from given string. The model contains a `string` with
  all escaped variable tokens written without escape symbol and `variables`
  property with all unescaped variables and their ranges
  @param  {String} string
  @return {Object}