Created
October 18, 2017 22:21
-
-
Save haskellcamargo/6b3749c7b95f3f37ed069efadb01f502 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <termios.h> | |
#include <unistd.h> | |
#include <ctype.h> | |
#include <string.h> | |
#define FG_MAGENTA "\x1B[35m" | |
#define FG_RESET "\x1B[0m" | |
/** | |
* Represents the REPL state which `render' will observe | |
*/ | |
struct s_php_repl_state { | |
char *line; | |
int column; | |
int history_index; | |
bool complete; | |
char *command; | |
char **history; | |
}; | |
/** | |
* REPL interface and essential functions to share state | |
*/ | |
typedef struct s_php_repl { | |
struct s_php_repl_state state; | |
void (*start)(struct s_php_repl *self); | |
void (*render)(struct s_php_repl *self); | |
void (*handle_read)(struct s_php_repl *self); | |
void (*tick)(struct s_php_repl *self, char chr); | |
} php_repl; | |
/** | |
* Disables the canonical mode for the terminal and returns the old | |
* configuration to be restored later | |
* | |
* @static | |
* @return struct termios | |
*/ | |
static struct termios disable_canonical_mode() { | |
struct termios old_term, new_term; | |
tcgetattr(STDIN_FILENO, &old_term); | |
new_term = old_term; | |
new_term.c_lflag &= ~ICANON; | |
new_term.c_lflag &= ~ECHO; | |
tcsetattr(STDIN_FILENO, TCSANOW, &new_term); | |
return old_term; | |
} | |
/** | |
* Receives and sets a terminal configuration | |
* | |
* @static | |
* @param struct termios term | |
* @return void | |
*/ | |
static void restore_canonical_mode(struct termios term) { | |
tcsetattr(STDIN_FILENO, TCSANOW, &term); | |
} | |
/** | |
* Emits the ANSI escape code to clear the current line | |
* | |
* @static | |
* @return void | |
*/ | |
static void console_clear_line() { | |
printf("\e[2K"); | |
} | |
/** | |
* Emits the ANSI escape code to reset the cursor for up to 1024 chars | |
* | |
* @static | |
* @return void | |
*/ | |
static void console_reset_cursor() { | |
printf("\e[1024D"); | |
} | |
/** | |
* Sets the initial state for the REPL | |
* | |
* @param php_repl *self | |
* @return void | |
*/ | |
static void php_repl_start(php_repl *self) { | |
struct s_php_repl_state state; | |
state.line = malloc(1); | |
state.column = 0; | |
state.history_index = 0; | |
state.complete = true; | |
state.command = NULL; | |
state.history = NULL; | |
self->state = state; | |
} | |
/** | |
* CLI renderer for the current line based on the state. Yes, we are using the React | |
* model with C, sorry | |
* | |
* @param php_repl *repl | |
* @return void | |
*/ | |
static void php_repl_render(php_repl *repl) { | |
struct s_php_repl_state state = repl->state; | |
char *line = state.line; | |
int column = state.column; | |
console_clear_line(); | |
console_reset_cursor(); | |
printf(FG_MAGENTA); | |
printf("php> "); | |
printf(FG_RESET); | |
printf("%s", state.line); | |
} | |
/** | |
* Handles terminal ticks and events using a char stream | |
* | |
* @param php_repl *repl | |
* @param char chr | |
* @return void | |
*/ | |
static void php_repl_tick(php_repl *repl, char chr) { | |
if (iscntrl(chr)) { | |
return; | |
} | |
struct s_php_repl_state state = repl->state; | |
char *line = state.line; | |
int column = state.column; | |
// Insert chat at specific index | |
char *buffer = malloc(sizeof(line) + sizeof(char)); | |
char holder[1] = { chr }; | |
strncpy(buffer, line, column); | |
int size = strlen(buffer); | |
strcpy(buffer + size, holder); | |
strcpy(buffer + size + 1, line + column); | |
repl->state.line = malloc(sizeof(buffer)); | |
repl->state.line = buffer; | |
repl->state.column++; | |
repl->render(repl); | |
} | |
/** | |
* Main loop for the line. Ends when the ENTER key is pressed | |
* | |
* @param php_repl *repl | |
* @return void | |
*/ | |
static void php_repl_handle_read(php_repl *repl) { | |
struct termios checkpoint = disable_canonical_mode(); | |
char current; | |
do { | |
current = getchar(); | |
repl->tick(repl, current); | |
} while (current != 0xA); | |
restore_canonical_mode(checkpoint); | |
} | |
/** | |
* Initializes the REPL functions | |
* | |
* @param php_repl *repl | |
* @return void | |
*/ | |
static void php_repl_init(php_repl *repl) { | |
repl->start = &php_repl_start; | |
repl->render = &php_repl_render; | |
repl->tick = &php_repl_tick; | |
repl->handle_read = &php_repl_handle_read; | |
} | |
/** | |
* Entry point for testing purposes | |
* | |
* @param int argc | |
* @param char **argv | |
* @return int | |
*/ | |
int main(int argc, char **argv) { | |
php_repl repl; | |
php_repl_init(&repl); | |
repl.start(&repl); | |
repl.render(&repl); | |
repl.handle_read(&repl); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment