/** * 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); }