2019-10-10 12:28:46 -04:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Kilo;
|
|
|
|
|
|
|
|
use FFI;
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/** @var FFI\CData $original_termios */
|
|
|
|
$original_termios = get_ffi()->new('struct termios');
|
2019-10-10 12:28:46 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// ! Raw mode / Terminal size
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function enable_raw_mode(): void
|
2019-10-10 12:28:46 -04:00
|
|
|
{
|
|
|
|
global $original_termios;
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
// Make sure to restore normal mode on exit/die/crash
|
2019-10-24 10:58:38 -04:00
|
|
|
register_shutdown_function('Kilo\disable_raw_mode');
|
2019-10-11 16:32:47 -04:00
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
$ffi = get_ffi();
|
|
|
|
|
2019-10-10 12:28:46 -04:00
|
|
|
// Populate the original terminal settings
|
2019-10-24 12:00:14 -04:00
|
|
|
$res = $ffi->tcgetattr(C::STDIN_FILENO, FFI::addr($original_termios));
|
2019-10-14 16:21:41 -04:00
|
|
|
if ($res === -1)
|
|
|
|
{
|
|
|
|
die('tcgetattr');
|
|
|
|
}
|
2019-10-10 12:28:46 -04:00
|
|
|
|
2019-10-21 15:37:20 -04:00
|
|
|
// So, the only thing that seems to really matter here is that c_oflag is 0...
|
|
|
|
$termios = clone $original_termios;
|
2019-10-24 12:00:14 -04:00
|
|
|
$termios->c_iflag = 0; //$termios->c_iflag & ~(C::BRKINT | C::ICRNL | C::INPCK | C::ISTRIP | C::IXON);
|
|
|
|
$termios->c_oflag = 0; // $termios->c_oflag && ~(C::OPOST);
|
|
|
|
$termios->c_cflag |= (C::CS8);
|
|
|
|
$termios->c_lflag = $termios->c_lflag & ~( C::ECHO | C::ICANON | C::IEXTEN | C::ISIG);
|
|
|
|
$termios->c_cc[C::VMIN] = 0;
|
|
|
|
$termios->c_cc[C::VTIME] = 1;
|
2019-10-10 12:28:46 -04:00
|
|
|
|
|
|
|
// Turn on raw mode
|
2019-10-24 12:00:14 -04:00
|
|
|
$res = $ffi->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
|
2019-10-14 16:21:41 -04:00
|
|
|
if ($res === -1)
|
|
|
|
{
|
|
|
|
die('tcsetattr');
|
|
|
|
}
|
2019-10-10 12:28:46 -04:00
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
function disable_raw_mode(): void
|
2019-10-10 12:28:46 -04:00
|
|
|
{
|
|
|
|
global $original_termios;
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
$ffi = get_ffi();
|
|
|
|
$res = $ffi->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($original_termios));
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-18 16:20:34 -04:00
|
|
|
echo "\n";
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
if ($res === -1)
|
|
|
|
{
|
|
|
|
die('tcsetattr');
|
|
|
|
}
|
2019-10-10 12:28:46 -04:00
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
/**
|
|
|
|
* @TODO fix
|
|
|
|
*/
|
|
|
|
function get_cursor_position()
|
2019-10-10 15:49:01 -04:00
|
|
|
{
|
2019-10-24 10:58:38 -04:00
|
|
|
write_stdout("\x1b[999C\x1b[999B");
|
|
|
|
write_stdout("\x1b[6n");
|
2019-10-10 12:28:46 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
$rows = 0;
|
|
|
|
$cols = 0;
|
2019-10-11 16:32:47 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
$buffer = read_stdout();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
$res = sscanf($buffer, '\x1b[%d;%dR', $rows, $cols);
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
if ($res === -1 || $buffer[0] !== '\x1b' || $buffer[1] !== '[')
|
|
|
|
{
|
|
|
|
die('Failed to get screen size');
|
|
|
|
}
|
|
|
|
|
|
|
|
return [$rows, $cols];
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
function get_window_size()
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-24 12:00:14 -04:00
|
|
|
$ffi = get_ffi();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
$ws = $ffi->new('struct winsize');
|
2019-10-24 12:00:14 -04:00
|
|
|
$res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
if ($res === -1 || $ws->ws_col === 0)
|
|
|
|
{
|
|
|
|
return get_cursor_position();
|
|
|
|
}
|
|
|
|
|
|
|
|
return [$ws->ws_row, $ws->ws_col];
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// ! C function/macro equivalents
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2019-10-22 16:16:28 -04:00
|
|
|
/**
|
|
|
|
* Do bit twiddling to convert a letter into
|
2019-10-24 10:58:38 -04:00
|
|
|
* its Ctrl-letter equivalent ordinal ascii value
|
2019-10-22 16:16:28 -04:00
|
|
|
*
|
|
|
|
* @param string $char
|
|
|
|
* @return int
|
|
|
|
*/
|
2019-10-11 16:32:47 -04:00
|
|
|
function ctrl_key(string $char): int
|
|
|
|
{
|
2019-10-24 10:58:38 -04:00
|
|
|
if ( ! is_ascii($char))
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
// b1,100,001 (a) & b0,011,111 (0x1f) = b0,000,001 (SOH)
|
|
|
|
// b1,100,010 (b) & b0,011,111 (0x1f) = b0,000,010 (STX)
|
|
|
|
// ...and so on
|
2019-10-11 16:32:47 -04:00
|
|
|
return ord($char) & 0x1f;
|
2019-10-22 17:50:35 -04:00
|
|
|
}
|
2019-10-23 13:34:40 -04:00
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* Does the one-character string contain an ascii ordinal value?
|
|
|
|
*
|
|
|
|
* @param string $single_char
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-10-24 10:58:38 -04:00
|
|
|
function is_ascii(string $single_char): bool
|
2019-10-23 13:34:40 -04:00
|
|
|
{
|
|
|
|
if (strlen($single_char) > 1)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ord($single_char) < 0x80;
|
|
|
|
}
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* Does the one-character string contain an ascii control character?
|
|
|
|
*
|
|
|
|
* @param string $char
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-10-24 10:58:38 -04:00
|
|
|
function is_cntrl(string $char): bool
|
2019-10-23 13:34:40 -04:00
|
|
|
{
|
|
|
|
$c = ord($char);
|
2019-10-24 10:58:38 -04:00
|
|
|
return is_ascii($char) && ( $c < 0x20 || $c === 0x7f );
|
2019-10-23 13:34:40 -04:00
|
|
|
}
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* Does the one-character string contain an ascii number?
|
|
|
|
*
|
|
|
|
* @param string $char
|
|
|
|
* @return bool
|
|
|
|
*/
|
2019-10-24 10:58:38 -04:00
|
|
|
function is_digit(string $char): bool
|
2019-10-23 13:34:40 -04:00
|
|
|
{
|
|
|
|
$c = ord($char);
|
2019-10-24 10:58:38 -04:00
|
|
|
return is_ascii($char) && ( $c > 0x2f && $c < 0x3a );
|
|
|
|
}
|
|
|
|
|
2019-10-24 16:22:52 -04:00
|
|
|
/**
|
|
|
|
* Does the one-character string contain ascii whitespace?
|
|
|
|
*
|
|
|
|
* @param string $char
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
function is_space(string $char): bool
|
|
|
|
{
|
|
|
|
$ws = [' ', "\t", "\n", "\r", "\xa", "\xb", "\xc"];
|
|
|
|
return is_ascii($char) && in_array($char, $ws, TRUE);
|
|
|
|
}
|
|
|
|
|
2019-10-24 10:58:38 -04:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// ! Helper functions
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* A 'singleton' function to replace a global variable
|
|
|
|
*
|
|
|
|
* @return FFI
|
|
|
|
*/
|
|
|
|
function get_ffi(): FFI
|
|
|
|
{
|
|
|
|
static $ffi;
|
|
|
|
|
|
|
|
if ($ffi === NULL)
|
|
|
|
{
|
|
|
|
$ffi = FFI::load(__DIR__ . '/ffi.h');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $ffi;
|
|
|
|
}
|
|
|
|
|
2019-10-24 16:22:52 -04:00
|
|
|
/**
|
|
|
|
* Does the one-character string contain a character that separates tokens?
|
|
|
|
*
|
|
|
|
* @param string $char
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
function is_separator(string $char): bool
|
|
|
|
{
|
|
|
|
if ( ! is_ascii($char))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// `strpos` is used instead of `strchr`/`strstr` as we don't care about the actual match
|
|
|
|
// while `strchr` would match the C version, it also returns the match
|
|
|
|
$isSep = (strpos(',.()+-/*=~%<>[];', $char) !== FALSE);
|
|
|
|
|
|
|
|
return is_space($char) || $char === "\0" || $isSep;
|
|
|
|
}
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* Pull input from the stdin stream.
|
|
|
|
*
|
|
|
|
* @param int $len
|
|
|
|
* @return string
|
|
|
|
*/
|
2019-10-24 10:58:38 -04:00
|
|
|
function read_stdin(int $len = 128): string
|
|
|
|
{
|
|
|
|
$handle = fopen('php://stdin', 'rb');
|
|
|
|
$input = fread($handle, $len);
|
|
|
|
fclose($handle);
|
|
|
|
|
|
|
|
return $input;
|
|
|
|
}
|
|
|
|
|
2019-10-24 12:00:14 -04:00
|
|
|
/**
|
|
|
|
* Write to the stdout stream
|
|
|
|
*
|
|
|
|
* @param string $str
|
|
|
|
* @param int|NULL $len
|
|
|
|
* @return int
|
|
|
|
*/
|
2019-10-24 10:58:38 -04:00
|
|
|
function write_stdout(string $str, int $len = NULL): int
|
|
|
|
{
|
|
|
|
$handle = fopen('php://stdout', 'ab');
|
|
|
|
$res = (is_int($len))
|
|
|
|
? fwrite($handle, $str, $len)
|
|
|
|
: fwrite($handle, $str);
|
|
|
|
|
|
|
|
fclose($handle);
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
function read_stdout(int $len = 128): string
|
|
|
|
{
|
|
|
|
$handle = fopen('php://stdout', 'rb');
|
|
|
|
$input = fread($handle, $len);
|
|
|
|
|
|
|
|
$input = rtrim($input);
|
|
|
|
fclose($handle);
|
|
|
|
|
|
|
|
return $input;
|
2019-10-23 13:34:40 -04:00
|
|
|
}
|
2019-10-24 12:00:14 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces a slice of an array with the same value
|
|
|
|
*
|
|
|
|
* @param array $array The array to update
|
|
|
|
* @param int $offset The index of the first location to update
|
|
|
|
* @param int $length The number of indices to update
|
|
|
|
* @param mixed $value The value to replace in the range
|
|
|
|
*/
|
|
|
|
function array_replace_range(array &$array, int $offset, int $length, $value):void
|
|
|
|
{
|
2019-10-25 15:35:20 -04:00
|
|
|
$replacement = array_fill(0, $length, $value);
|
|
|
|
array_splice($array, $offset, $length, $replacement);
|
|
|
|
|
|
|
|
/* $end = $offset + $length;
|
2019-10-24 12:00:14 -04:00
|
|
|
for ($i = $offset; $i < $end; $i++)
|
|
|
|
{
|
|
|
|
$array[$i] = $value;
|
2019-10-25 15:35:20 -04:00
|
|
|
} */
|
2019-10-24 12:00:14 -04:00
|
|
|
}
|
2019-10-24 16:57:27 -04:00
|
|
|
|
2019-10-29 17:02:03 -04:00
|
|
|
/**
|
|
|
|
* Get the ASCII color escape number for the specified syntax type
|
|
|
|
*
|
|
|
|
* @param int $hl
|
|
|
|
* @return int
|
|
|
|
*/
|
2019-10-24 16:57:27 -04:00
|
|
|
function syntax_to_color(int $hl): int
|
|
|
|
{
|
2019-10-25 15:35:20 -04:00
|
|
|
$map = [
|
|
|
|
Highlight::COMMENT => 36, // Foreground Cyan,
|
2019-10-25 16:36:03 -04:00
|
|
|
Highlight::ML_COMMENT => 90, // Foreground Bright Black
|
2019-10-25 15:35:20 -04:00
|
|
|
Highlight::KEYWORD1 => 33, // Foreground Yellow
|
|
|
|
Highlight::KEYWORD2 => 32, // Foreground Green
|
|
|
|
Highlight::STRING => 35, // Foreground Magenta
|
|
|
|
Highlight::NUMBER => 31, // Foreground Red
|
2019-10-29 17:02:03 -04:00
|
|
|
Highlight::OPERATOR => 92, // Foreground Bright Green
|
|
|
|
Highlight::VARIABLE => 96, // Foreground Bright Cyan
|
|
|
|
Highlight::INVALID => 101, // Background Bright Red
|
2019-10-25 16:36:03 -04:00
|
|
|
Highlight::MATCH => 7, // Reverse!
|
2019-10-25 15:35:20 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
return (array_key_exists($hl, $map))
|
|
|
|
? $map[$hl]
|
|
|
|
: 37; // Foreground White
|
2019-10-24 16:57:27 -04:00
|
|
|
}
|
2019-10-29 17:02:03 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Use 'token_get_all' to get the tokens for a file,
|
|
|
|
* organized by row number
|
|
|
|
*
|
|
|
|
* @param string $filename
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
function get_php_tokens(string $filename): array
|
|
|
|
{
|
|
|
|
$raw_tokens = token_get_all(file_get_contents($filename), TOKEN_PARSE);
|
|
|
|
$tokens = [];
|
|
|
|
$lineNum = 1;
|
|
|
|
$line = [];
|
|
|
|
foreach($raw_tokens as $token)
|
|
|
|
{
|
|
|
|
// Simple characters, usually delimiters
|
|
|
|
if ( ! is_array($token))
|
|
|
|
{
|
|
|
|
$line[] = [
|
|
|
|
'typeName' => 'RAW',
|
|
|
|
'char' => $token,
|
|
|
|
];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
[$type, $char, $currentLine] = $token;
|
|
|
|
|
|
|
|
$current = [
|
|
|
|
'type' => $type,
|
|
|
|
'typeName' => token_name($type),
|
|
|
|
'char' => $char,
|
|
|
|
'line' => $currentLine,
|
|
|
|
];
|
|
|
|
|
|
|
|
if ($current['line'] !== $lineNum)
|
|
|
|
{
|
|
|
|
$tokens[$lineNum] = $line;
|
|
|
|
$lineNum = $current['line'];
|
|
|
|
$line = [];
|
|
|
|
$line[] = $current;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$line[] = $current;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tokens;
|
|
|
|
}
|