/**
  * Parse heredoc/nowdoc syntax
  *
  * @param  StringReader $input
  * @return array|null
  */
 protected function parseHeredoc(StringReader $input)
 {
     $startLine = $input->line;
     $startIndex = $input->i - 3;
     // detect nowdoc syntax
     if ('\'' === $input->char) {
         $isNowdoc = true;
         $input->eat();
     } else {
         $isNowdoc = false;
     }
     // parse identifier
     $idt = '';
     while (StringReader::CHAR_IDT === $input->charType()) {
         $idt .= $input->shift();
     }
     if ('' === $idt) {
         return;
     }
     if ($isNowdoc) {
         if ('\'' !== $input->char) {
             return;
         }
         $input->eat();
     }
     // determine end sequence
     $line = '';
     if ("\r" === $input->char) {
         $line .= $input->shift();
     }
     if ("\n" === $input->char) {
         $line .= $input->shift();
     }
     if ('' === $line) {
         return;
     }
     $end = "{$line}{$idt}";
     // find end
     $endLen = strlen($end);
     $endOffset = 0;
     while (!$input->end) {
         // end detection
         if ($input->char === $end[$endOffset]) {
             ++$endOffset;
             if ($endLen === $endOffset) {
                 $input->eat();
                 // skip semicolon
                 if (';' === $input->char) {
                     $input->eat();
                 }
                 // validate end of line
                 for ($ii = 0; isset($line[$ii]); ++$ii) {
                     if ($line[$ii] !== $input->char) {
                         $ii = false;
                         break;
                     }
                     $input->eat();
                 }
                 // full end match?
                 if (false !== $ii) {
                     break;
                 } else {
                     $endOffset = 0;
                 }
             }
         } elseif (0 !== $endOffset) {
             $endOffset = 0;
             continue;
         }
         $input->eat();
     }
     // return token
     return $this->token($isNowdoc ? self::T_NOWDOC : self::T_HEREDOC, $startLine, substr($input->str, $startIndex, $input->i - $startIndex));
 }
 /**
  * Parse string
  *
  * Pre-offset: after ' or "
  * Post-offset: after ' or "
  *
  * @param  StringReader $input input
  * @param  int          $line  line number
  * @param  string       $quote used quote syntax
  * @return array
  */
 protected function parseString(StringReader $input, $line, $quote)
 {
     $buffer = $quote;
     $escaped = false;
     // parse
     while (!$input->end) {
         // handle char
         switch ($input->char) {
             // escape symbol
             case '\\':
                 $escaped = !$escaped;
                 $buffer .= '\\';
                 break;
                 // quote
             // quote
             case $quote:
                 // end of string?
                 if (!$escaped) {
                     $buffer .= $quote;
                     $input->eat();
                     // skip quote
                     break 2;
                 }
                 // other chars
             // other chars
             default:
                 $escaped = false;
                 // reset escaing
                 $buffer .= $input->char;
                 break;
         }
         // advance input
         $input->eat();
     }
     // return token
     return $this->token(self::T_STRING, $line, $buffer);
 }