2019-11-14 12:14:02 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Aviat\Kilo\Tokens;
|
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
use function Aviat\Kilo\str_contains;
|
2019-11-14 12:14:02 -05:00
|
|
|
use function Aviat\Kilo\tabs_to_spaces;
|
|
|
|
|
|
|
|
class PHP {
|
2019-11-14 17:11:10 -05:00
|
|
|
|
|
|
|
private string $code;
|
|
|
|
|
|
|
|
private array $rawLines;
|
|
|
|
|
2019-11-15 11:35:08 -05:00
|
|
|
private array $tokens;
|
2019-11-14 17:11:10 -05:00
|
|
|
|
|
|
|
private int $lineNum = 1;
|
|
|
|
|
|
|
|
private function __construct(string $code)
|
|
|
|
{
|
|
|
|
$lines = explode("\n", $code);
|
|
|
|
array_unshift($lines, '');
|
|
|
|
unset($lines[0]);
|
|
|
|
|
|
|
|
$this->code = $code;
|
|
|
|
$this->rawLines = $lines;
|
2019-11-15 16:19:34 -05:00
|
|
|
$this->tokens = array_fill(1, count($lines), []);
|
2019-11-14 17:11:10 -05:00
|
|
|
}
|
|
|
|
|
2019-11-14 12:14:02 -05:00
|
|
|
/**
|
|
|
|
* Use 'token_get_all' to get the tokens for a file,
|
|
|
|
* organized by row number
|
|
|
|
*
|
|
|
|
* @param string $code
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getTokens(string $code): array
|
|
|
|
{
|
2019-11-14 17:11:10 -05:00
|
|
|
return (new self($code))->organizeTokens();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return tokens for the current $filename, organized
|
|
|
|
* by row number
|
|
|
|
*
|
|
|
|
* @param string $filename
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getFileTokens(string $filename): array
|
|
|
|
{
|
2019-11-19 15:57:51 -05:00
|
|
|
$code = @file_get_contents($filename);
|
2019-11-14 17:11:10 -05:00
|
|
|
|
|
|
|
if ($code === FALSE)
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::getTokens($code);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function organizeTokens(): array
|
|
|
|
{
|
|
|
|
$rawTokens = token_get_all($this->code);
|
|
|
|
foreach ($rawTokens as $t)
|
2019-11-14 12:14:02 -05:00
|
|
|
{
|
|
|
|
if (is_array($t))
|
|
|
|
{
|
2019-11-14 17:11:10 -05:00
|
|
|
$this->processArrayToken($t);
|
|
|
|
}
|
|
|
|
else if (is_string($t))
|
|
|
|
{
|
|
|
|
$this->processStringToken($t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ksort($this->tokens);
|
|
|
|
|
|
|
|
return $this->tokens;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function processArrayToken(array $token): void
|
|
|
|
{
|
|
|
|
[$type, $rawChar, $currentLine] = $token;
|
|
|
|
$char = tabs_to_spaces($rawChar);
|
|
|
|
|
2019-11-18 13:45:36 -05:00
|
|
|
$this->lineNum = $currentLine;
|
2019-11-15 11:35:08 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
$current = [
|
|
|
|
'type' => $type,
|
|
|
|
'typeName' => token_name($type),
|
|
|
|
'char' => $char,
|
|
|
|
'line' => $currentLine,
|
|
|
|
];
|
|
|
|
|
2019-11-15 11:35:08 -05:00
|
|
|
// Single new line, or starts with a new line with other whitespace
|
|
|
|
if (strpos($char, "\n") === 0 && trim($char) === '')
|
2019-11-14 17:11:10 -05:00
|
|
|
{
|
2019-11-15 11:35:08 -05:00
|
|
|
$this->tokens[$this->lineNum][] = $current;
|
2019-11-14 17:11:10 -05:00
|
|
|
$this->lineNum++;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2019-11-14 12:14:02 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
// Only return the first line of a multi-line token for this line array
|
|
|
|
if (str_contains($char, "\n"))
|
|
|
|
{
|
|
|
|
$chars = explode("\n", $char);
|
|
|
|
$current['original'] = [
|
|
|
|
'string' => $char,
|
|
|
|
'lines' => $chars,
|
|
|
|
];
|
|
|
|
$current['char'] = array_shift($chars);
|
|
|
|
|
|
|
|
// Add new lines for additional newline characters
|
|
|
|
$nextLine = $currentLine;
|
|
|
|
foreach ($chars as $char)
|
|
|
|
{
|
|
|
|
$nextLine++;
|
2019-11-14 12:14:02 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
if ( ! empty($char))
|
2019-11-14 12:14:02 -05:00
|
|
|
{
|
2019-11-14 17:11:10 -05:00
|
|
|
$this->processStringToken($char, $nextLine);
|
2019-11-14 12:14:02 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
$this->tokens[$this->lineNum][] = $current;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function processStringToken(string $token, ?int $startLine = NULL): void
|
|
|
|
{
|
|
|
|
$char = tabs_to_spaces($token);
|
2019-11-14 12:14:02 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
$startLine = $startLine ?? $this->lineNum;
|
|
|
|
$lineNumber = $this->findCorrectLine($char, $startLine) ?? $startLine;
|
2019-11-14 12:14:02 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
// Simple characters, usually delimiters or single character operators
|
|
|
|
$this->tokens[$lineNumber][] = [
|
|
|
|
'type' => -1,
|
|
|
|
'typeName' => 'RAW',
|
|
|
|
'char' => tabs_to_spaces($token),
|
|
|
|
];
|
2019-11-14 12:14:02 -05:00
|
|
|
}
|
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
private function findCorrectLine(string $search, int $rowOffset, int $searchLength = 5): ?int
|
2019-11-14 12:14:02 -05:00
|
|
|
{
|
2019-11-14 17:11:10 -05:00
|
|
|
$end = $rowOffset + $searchLength;
|
|
|
|
if ($end > count($this->rawLines))
|
|
|
|
{
|
|
|
|
$end = count($this->rawLines);
|
|
|
|
}
|
2019-11-14 12:14:02 -05:00
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
for ($i = $rowOffset; $i < $end; $i++)
|
2019-11-14 12:14:02 -05:00
|
|
|
{
|
2019-11-14 17:11:10 -05:00
|
|
|
if (str_contains($this->rawLines[$i], $search))
|
|
|
|
{
|
|
|
|
return $i;
|
|
|
|
}
|
2019-11-14 12:14:02 -05:00
|
|
|
}
|
|
|
|
|
2019-11-14 17:11:10 -05:00
|
|
|
return NULL;
|
2019-11-14 12:14:02 -05:00
|
|
|
}
|
|
|
|
}
|