php-kilo/src/functions.php

663 lines
12 KiB
PHP

<?php declare(strict_types=1);
namespace Aviat\Kilo;
use FFI;
use Aviat\Kilo\Enum\{C, Color, Highlight, KeyCode};
/**
* See if tput exists for fallback terminal size detection
*
* @return bool
* @codeCoverageIgnore
*/
function has_tput(): bool
{
$cmd = shell_exec('type tput');
if ( ! is_string($cmd))
{
return FALSE;
}
return str_contains($cmd, ' is ');
}
// ----------------------------------------------------------------------------
// ! Terminal size
// ----------------------------------------------------------------------------
/**
* Get the size of the current terminal window
*
* @codeCoverageIgnore
* @return array
*/
function get_window_size(): array
{
// First, try to get the answer from ioctl
$ffi = get_ffi();
$ws = $ffi->new('struct winsize');
if ($ws !== NULL)
{
$res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
{
return [$ws->ws_row, $ws->ws_col];
}
}
// Try using tput
if (has_tput())
{
$rows = (int)trim(shell_exec('tput lines'));
$cols = (int)trim(shell_exec('tput cols'));
if ($rows > 0 && $cols > 0)
{
return [$rows, $cols];
}
}
// Worst-case, return an arbitrary 'standard' size
return [25, 80];
}
// ----------------------------------------------------------------------------
// ! C function/macro equivalents
// ----------------------------------------------------------------------------
/**
* Do bit twiddling to convert a letter into
* its Ctrl-letter equivalent ordinal ascii value
*
* @param string $char
* @return int
*/
function ctrl_key(string $char): int
{
if ( ! is_ascii($char))
{
return -1;
}
// 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
return ord($char) & 0x1f;
}
/**
* Does the one-character string contain an ascii ordinal value?
*
* @param string $single_char
* @return bool
*/
function is_ascii(string $single_char): bool
{
if (strlen($single_char) > 1)
{
return FALSE;
}
return ord($single_char) < 0x80;
}
/**
* Does the one-character string contain an ascii control character?
*
* @param string $char
* @return bool
*/
function is_ctrl(string $char): bool
{
$c = ord($char);
return is_ascii($char) && ( $c === 0x7f || $c < 0x20 );
}
/**
* Does the one-character string contain an ascii number?
*
* @param string $char
* @return bool
*/
function is_digit(string $char): bool
{
$c = ord($char);
return is_ascii($char) && ( $c > 0x2f && $c < 0x3a );
}
/**
* Does the one-character string contain ascii whitespace?
*
* @param string $char
* @return bool
*/
function is_space(string $char): bool
{
return match($char) {
KeyCode::CARRIAGE_RETURN,
KeyCode::FORM_FEED,
KeyCode::NEWLINE,
KeyCode::SPACE,
KeyCode::TAB,
KeyCode::VERTICAL_TAB => true,
default => false,
};
}
// ----------------------------------------------------------------------------
// ! Helper functions
// ----------------------------------------------------------------------------
/**
* 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;
}
/**
* 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;
}
$isSep = str_contains(',.()+-/*=~%<>[];', $char);
return is_space($char) || $char === KeyCode::NULL || $isSep;
}
/**
* Pull input from the stdin stream.
*
* @codeCoverageIgnore
* @param int $len
* @return string
*/
function read_stdin(int $len = 128): string
{
$handle = fopen('php://stdin', 'rb');
if ($handle === false)
{
return '';
}
$input = fread($handle, $len);
fclose($handle);
return (is_string($input)) ? $input : '';
}
/**
* Write to the stdout stream
*
* @codeCoverageIgnore
* @param string $str
* @param int|NULL $len
* @return int|false
*/
function write_stdout(string $str, int $len = NULL): int|false
{
$handle = fopen('php://stdout', 'ab');
if ($handle === false)
{
return false;
}
$res = (is_int($len))
? fwrite($handle, $str, $len)
: fwrite($handle, $str);
fclose($handle);
return $res;
}
/**
* 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
{
if ($length === 1)
{
$array[$offset] = $value;
return;
}
$replacement = array_fill(0, $length, $value);
array_splice($array, $offset, $length, $replacement);
}
/**
* Does the string $haystack contain $str, optionally searching from $offset?
*
* @param string $haystack
* @param string $str
* @param int|null $offset
* @return bool
*/
function str_contains(string $haystack, string $str, ?int $offset = NULL): bool
{
if (empty($str))
{
return FALSE;
}
return ($offset !== NULL)
? strpos($haystack, $str, $offset) !== FALSE
: \str_contains($haystack, $str);
}
/**
* Get the ASCII color escape number for the specified syntax type
*
* @param int $hl
* @return int
*/
function syntax_to_color(int $hl): int
{
return match ($hl)
{
Highlight::COMMENT => Color::FG_CYAN,
Highlight::ML_COMMENT => Color::FG_BRIGHT_BLACK,
Highlight::KEYWORD1 => Color::FG_YELLOW,
Highlight::KEYWORD2 => Color::FG_GREEN,
Highlight::STRING => Color::FG_MAGENTA,
Highlight::NUMBER => Color::FG_BRIGHT_RED,
Highlight::OPERATOR => Color::FG_BRIGHT_GREEN,
Highlight::VARIABLE => Color::FG_BRIGHT_CYAN,
Highlight::DELIMITER => Color::FG_BLUE,
Highlight::INVALID => Color::BG_BRIGHT_RED,
Highlight::MATCH => Color::INVERT,
Highlight::IDENTIFIER => Color::FG_BRIGHT_WHITE,
default => Color::FG_WHITE,
};
}
/**
* Replace tabs with the specified number of spaces.
*
* @param string $str
* @param int $number
* @return string
*/
function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
{
return str_replace(KeyCode::TAB, str_repeat(KeyCode::SPACE, $number), $str);
}
/**
* Generate/Get the syntax highlighting objects
*
* @return array
*/
function get_file_syntax_map(): array
{
static $db = [];
if (count($db) === 0)
{
$db = [
Syntax::new(
'C',
['.c', '.h', '.cpp'],
[
'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct',
'union', 'class', 'else', 'enum', 'for', 'case', 'if',
],
[
'#include', 'unsigned', '#define', '#ifndef', 'double', 'signed', '#endif',
'#ifdef', 'float', '#error', '#undef', 'long', 'char', 'int', 'void', '#if',
],
'//',
'/*',
'*/',
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
),
Syntax::new(
'CSS',
['.css', '.less', '.sass', 'scss'],
[],
[],
'',
'/*',
'*/',
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
),
Syntax::new(
'JavaScript',
['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'],
[
'instanceof',
'continue',
'debugger',
'function',
'default',
'extends',
'finally',
'delete',
'export',
'import',
'return',
'switch',
'typeof',
'break',
'catch',
'class',
'const',
'super',
'throw',
'while',
'yield',
'case',
'else',
'this',
'void',
'with',
'from',
'for',
'new',
'try',
'var',
'do',
'if',
'in',
'as',
],
[
'=>', 'Number', 'String', 'Object', 'Math', 'JSON', 'Boolean',
],
'//',
'/*',
'*/',
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
),
Syntax::new(
'PHP',
['.php', 'kilo'],
[
'?php', '$this', '__halt_compiler'
],
[
'int', 'float', 'bool', 'string', 'true', 'TRUE', 'false', 'FALSE', 'null', 'NULL',
'void', 'iterable', 'object', 'strict_types'
],
'//',
'/*',
'*/',
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
),
Syntax::new(
'Rust',
['.rs'],
[
'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',
'loop', 'move', 'self', 'type', 'while', 'for', 'let', 'mod', 'pub', 'ref', 'true',
'use', 'mut', 'as', 'fn', 'if', 'in',
],
[
'DoubleEndedIterator',
'ExactSizeIterator',
'IntoIterator',
'PartialOrd',
'PartialEq',
'Iterator',
'ToString',
'Default',
'ToOwned',
'Extend',
'FnOnce',
'Option',
'String',
'AsMut',
'AsRef',
'Clone',
'Debug',
'FnMut',
'Sized',
'Unpin',
'array',
'isize',
'usize',
'&str',
'Copy',
'Drop',
'From',
'Into',
'None',
'Self',
'Send',
'Some',
'Sync',
'Sync',
'bool',
'char',
'i128',
'u128',
'Box',
'Err',
'Ord',
'Vec',
'dyn',
'f32',
'f64',
'i16',
'i32',
'i64',
'str',
'u16',
'u32',
'u64',
'Eq',
'Fn',
'Ok',
'i8',
'u8',
],
'//',
'/*',
'*/',
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
),
];
}
return $db;
}
function php_token_to_highlight(int $token): int
{
return match($token) {
// Delimiters
T_ARRAY,
T_CURLY_OPEN,
T_DOLLAR_OPEN_CURLY_BRACES,
T_OPEN_TAG,
T_OPEN_TAG_WITH_ECHO,
T_CLOSE_TAG,
T_START_HEREDOC,
T_END_HEREDOC => Highlight::DELIMITER,
// Number literals and magic constants
T_CLASS_C,
T_DIR,
T_DNUMBER,
T_LNUMBER,
T_FILE,
T_FUNC_C,
T_LINE,
T_METHOD_C,
T_NS_C,
T_NUM_STRING,
T_TRAIT_C => Highlight::NUMBER,
// String literals
T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE => Highlight::STRING,
// Simple variables
T_VARIABLE, T_STRING_VARNAME => Highlight::VARIABLE,
// Operators
T_AS,
T_AND_EQUAL,
T_BOOLEAN_AND,
T_BOOLEAN_OR,
T_COALESCE,
T_COALESCE_EQUAL,
T_CONCAT_EQUAL,
T_DEC,
T_DIV_EQUAL,
T_DOUBLE_ARROW,
T_DOUBLE_COLON,
T_ELLIPSIS,
T_INC,
T_INSTANCEOF,
T_INSTEADOF,
T_IS_EQUAL,
T_IS_GREATER_OR_EQUAL,
T_IS_IDENTICAL,
T_IS_NOT_EQUAL,
T_IS_NOT_IDENTICAL,
T_IS_SMALLER_OR_EQUAL,
T_SPACESHIP,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_LOGICAL_XOR,
T_MINUS_EQUAL,
T_MOD_EQUAL,
T_MUL_EQUAL,
T_NS_SEPARATOR,
T_NULLSAFE_OBJECT_OPERATOR,
T_OBJECT_OPERATOR,
T_OR_EQUAL,
T_PLUS_EQUAL,
T_POW,
T_POW_EQUAL,
T_SL,
T_SL_EQUAL,
T_SR,
T_SR_EQUAL,
T_XOR_EQUAL => Highlight::OPERATOR,
// Keywords1
T_ABSTRACT,
T_BREAK,
T_CASE,
T_CATCH,
T_CLASS,
T_CLONE,
T_CONST,
T_CONTINUE,
T_DECLARE,
T_DEFAULT,
T_DO,
T_ECHO,
T_ELSE,
T_ELSEIF,
T_EMPTY,
T_ENDDECLARE,
T_ENDFOR,
T_ENDFOREACH,
T_ENDIF,
T_ENDSWITCH,
T_ENDWHILE,
T_EVAL,
T_EXIT,
T_EXTENDS,
T_FINAL,
T_FINALLY,
T_FN,
T_FOR,
T_FOREACH,
T_FUNCTION,
T_GLOBAL,
T_GOTO,
T_HALT_COMPILER,
T_IF,
T_IMPLEMENTS,
T_INCLUDE,
T_INCLUDE_ONCE,
T_INTERFACE,
T_ISSET,
T_LIST,
T_MATCH,
T_NAMESPACE,
T_NEW,
T_PRINT,
T_PRIVATE,
T_PUBLIC,
T_PROTECTED,
T_REQUIRE,
T_REQUIRE_ONCE,
T_RETURN,
T_STATIC,
T_SWITCH,
T_THROW,
T_TRAIT,
T_TRY,
T_UNSET,
T_USE,
T_VAR,
T_WHILE,
T_YIELD,
T_YIELD_FROM => Highlight::KEYWORD1,
// Not string literals, but identifiers, keywords, etc.
T_STRING => Highlight::IDENTIFIER,
// Types and casts
T_ARRAY_CAST,
T_BOOL_CAST,
T_CALLABLE,
T_DOUBLE_CAST,
T_INT_CAST,
T_OBJECT_CAST,
T_STRING_CAST,
T_UNSET_CAST => Highlight::KEYWORD2,
// Invalid syntax
T_BAD_CHARACTER => Highlight::INVALID,
default => Highlight::NORMAL,
};
}
function php_char_to_highlight(string $char): int
{
return match ($char) {
// Delimiter characters
'[', ']', '{', '}', '(', ')', '"', "'" => Highlight::DELIMITER,
// Single character operators
'?', ',', ';', ':', '^', '%', '+', '-',
'*', '/', '.', '|', '~', '>', '<', '=', '!' => Highlight::OPERATOR,
default => Highlight::NORMAL,
};
}