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, }; }