Skip to content

Instantly share code, notes, and snippets.

@haskellcamargo
Created October 18, 2017 22:21
Show Gist options
  • Save haskellcamargo/6b3749c7b95f3f37ed069efadb01f502 to your computer and use it in GitHub Desktop.
Save haskellcamargo/6b3749c7b95f3f37ed069efadb01f502 to your computer and use it in GitHub Desktop.
#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