static parseSyntaxTree(str, state, arr) {

    arr = arr || [];
    state = state || 'NAME';

    let punc = '():{},';
    let nameRE = /[_A-Za-z][_0-9A-Za-z]*/;

    const STATES = {
      'NAME': {
        skip: false,
        terminate: true,
        next: 'PROPERTYLIST',
        func: (str, arr) => {

          let match = str.match(nameRE);
          let name = match ? match[0] : null;

          arr.push({
            type: 'field',
            data: {
              name: name
            }
          });

          let len = name ? name.length : 0;
          return str.substr(len);

        }
      },
      'PROPERTYNAME': {
        skip: false,
        terminate: true,
        next: 'PROPERTYVALUESTART',
        func: (str, arr) => {

          let match = str.match(nameRE);
          let name = match ? match[0] : null;

          arr.push({
            type: 'property',
            data: {
              name: name
            }
          });

          let len = name ? name.length : 0;
          return str.substr(len);

        }
      },
      'PROPERTYVALUESTART': {
        skip: false,
        terminate: false,
        next: 'PROPERTYVALUE',
        func: (str, arr) => {

          if (str[0] !== ':') {
            return str;
          }

          return str.substr(1);

        }
      },
      'PROPERTYVALUE': {
        skip: false,
        terminate: false,
        next: 'PROPERTYVALUEEND',
        func: (str, arr) => {

          let cur = arr[arr.length - 1];

          if (str[0] !== '"') {

            let items = [
              {str: 'null', val: null},
              {str: 'true', val: true},
              {str: 'false', val: false}
            ];

            for (let i = 0; i < items.length; i++) {
              let item = items[i];
              if (str.substr(0, item.str.length) === item.str) {
                cur.data.value = item.val;
                return str.substr(item.str.length);
              }
            }

            let value = str.match(/^[\-\+]?\d+(\.\d+|e[\-\+]?\d+)?/i);

            if (!value) {
              return str;
            }

            value = value[0];
            cur.data.value = parseFloat(value);
            return str.substr(value.length);

          }

          let i = 1;
          while (str[i]) {

            if (str[i] === '"') {

              let n = 1;
              let c = 0;

              while (str[i - n] === '\\') {
                c++;
                n++;
              }

              if (!(c & 1)) {
                cur.data.value = str.substring(1, i);
                return str.substring(i + 1);
              }

            }

            i++;

          }

          return str;

        }
      },
      'PROPERTYVALUEEND': {
        skip: false,
        terminate: true,
        next: 'PROPERTYNAME',
        func: (str, arr) => {

          if (str[0] !== ',') {
            return str;
          }

          return str.substr(1);

        }
      },
      'PROPERTYLIST': {
        skip: true,
        terminate: true,
        func: (str, arr) => {

          if (str[0] !== '(') {
            return str;
          }

          let cur = arr[arr.length - 1];

          let count = 0;
          let i = 0;

          while (str[i]) {
            if (str[i] === '(') {
              count++;
            } else if (str[i] === ')') {
              count--;
            }
            if (!count) {
              break;
            }
            i++;
          }

          if (count) {
            return str;
          }

          cur.data.properties = this.parseSyntaxTree(str.substring(1, i), 'PROPERTYNAME');

          return str.substring(i + 1);

        },
        next: 'LIST'
      },
      'LIST': {
        skip: true,
        terminate: true,
        next: 'NAMEEND',
        func: (str, arr) => {

          if (str[0] !== '{') {
            return str;
          }

          let cur = arr[arr.length - 1];

          let count = 0;
          let i = 0;

          while (str[i]) {
            if (str[i] === '{') {
              count++;
            } else if (str[i] === '}') {
              count--;
            }
            if (!count) {
              break;
            }
            i++;
          }

          if (count) {
            return str;
          }

          cur.data.children = this.parseSyntaxTree(str.substring(1, i), 'NAME');

          return str.substring(i + 1);

        }
      },
      'NAMEEND': {
        skip: false,
        terminate: true,
        next: 'NAME',
        func: (str, arr) => {

          if (str[0] !== ',') {
            return str;
          }

          return str.substr(1);

        }
      }
    };

    /* State machine... */

    str = str.replace(/^\s*(.*)$/m, '$1');

    if (!str) {
      if (STATES[state].terminate) {
        return arr;
      } else {
        throw new Error('Unexpected termination');
      }
    }

    // Execute next step...
    let next = STATES[state].func(str, arr);

    if (!STATES[state].skip && (next === str)) {
      throw new Error(`Syntax Error at or near "${str.substr(0, 20)}"`);
    }

    if (!STATES[state].next) {
      return arr;
    }

    return this.parseSyntaxTree(next, STATES[state].next, arr);

  }
Parse syntax tree of a GraphQL query