/**
  * 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 identifier
  *
  * Pre-offset: at idt char
  * Post-offset: at first non-idt char
  *
  * @param  StringReader $input input
  * @return array
  */
 protected function parseIdt(StringReader $input)
 {
     $line = $input->line;
     // gather identifier characters
     $buffer = '';
     while (StringReader::CHAR_IDT === ($charType = $input->charType()) || StringReader::CHAR_NUM === $charType) {
         $buffer .= $input->shift();
     }
     // determine token type
     if (isset($this->vars[$buffer[0]])) {
         $id = self::T_VARIABLE;
     } elseif (isset($this->keywords[$buffer])) {
         $id = self::T_KEYWORD;
     } else {
         $id = self::T_IDENTIFIER;
     }
     // return token
     return $this->token($id, $line, $buffer);
 }