private function parser(string $data, callable $cast = null, string $delimiter = ','): \Generator
    {
        if ($data[0] !== '{' || \substr($data, -1) !== '}') {
            throw new ParseException("Missing opening or closing brackets");
        }

        $data = \ltrim(\substr($data, 1));

        do {
            if ($data === '') {
                throw new ParseException("Missing closing bracket");
            }

            if ($data[0] === '{') { // Array
                $parser = $this->parser($data, $cast, $delimiter);
                yield \iterator_to_array($parser);
                $data = $parser->getReturn();
                $end = $this->trim($data, 0, $delimiter);
                continue;
            }

            if ($data[0] === '"') { // Quoted value
                for ($position = 1; isset($data[$position]); ++$position) {
                    if ($data[$position] === '\\') {
                        ++$position; // Skip next character
                        continue;
                    }

                    if ($data[$position] === '"') {
                        break;
                    }
                }

                if (!isset($data[$position])) {
                    throw new ParseException("Could not find matching quote in quoted value");
                }

                $yield = \stripslashes(\substr($data, 1, $position - 1));

                $end = $this->trim($data, $position + 1, $delimiter);
            } else { // Unquoted value
                $position = 0;
                while (isset($data[$position]) && $data[$position] !== $delimiter && $data[$position] !== '}') {
                    ++$position;
                }

                $yield = \trim(\substr($data, 0, $position));

                $end = $this->trim($data, $position, $delimiter);

                if (\strcasecmp($yield, "NULL") === 0) { // Literal NULL is always unquoted.
                    yield null;
                    continue;
                }
            }

            yield $cast ? $cast($yield) : $yield;
        } while ($end !== '}');

        return $data;
    }