Michael Sanders <
porkchop@invalid.foo> writes:
/*
* mini_repl.c ? Mini REPL with persistent history
* Michael Sanders - 2025 use as you see fit
*
* repl commands:
* .q quit
* .h show short help
* .c clear the screen
*
* Full persistent history (~/.mini_repl_history)
* via up/down keys, no history duplicates.
*
* Build: cc mini_repl.c -o mini_repl
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <termios.h>
#include <limits.h>
#define MAXLINE 1024
#define HISTORY_MAX 128
#define HISTFILE ".mini_repl_history"
static char *history[HISTORY_MAX];
static int hist_count = 0;
static int hist_index = -1;
static char hist_path[PATH_MAX];
/* -------- Helpers -------- */
static void clear_screen(void) {
fputs("\033[2J\033[H", stdout);
fflush(stdout);
}
I think you call this only when the user requests it, which is fine.
(I've seen too many programs that gratuitiously clear the screen,
possibly erasing the user's information from previous commands.)
You have several string literals containing terminal control sequences.
Your code would be clearer if you had named "constants" for them.
static void load_history(void) {
const char *home = getenv("HOME");
if (home) snprintf(hist_path, sizeof(hist_path), "%s/%s", home, HISTFILE);
else strncpy(hist_path, HISTFILE, sizeof(hist_path) - 1);
strncpy is not just a "safer" version of strcpy. It can leave the
target array unterminated (not containing a string). I've written about
it here:
https://the-flat-trantor-society.blogspot.com/2012/03/no-strncpy-is-not-safer-strcpy.html
If you want something like strcpy that truncates safely, set
hist_path[0] to '\0' and use strncat. But consider whether truncation
is really what you want. Imagine that a command containing a command
like "rm -rf /home/yourname/temporary_directory" is truncated to "rm -rf /home/yourname/". Consider treating an overly long input as an error,
at least for now. Later you can consider updating your program to
handle arbitrarily long lines gracefully.
PATH_MAX is typically 4096, so you're not likely to run into it -- but
that makes it difficult to test your program's behavior with long lines.
FILE *fp = fopen(hist_path, "r");
if (!fp) return;
char line[MAXLINE];
while (fgets(line, sizeof(line), fp) && hist_count < HISTORY_MAX) {
line[strcspn(line, "\r\n")] = '\0';
It's unlikely that line will contain a '\r' character, even on Windows.
In text mode, line endings are translated to a single '\n' on input, and
vice versa on output. (On Unix-like systems, this translation is trivial.)
If line doesn't contain a '\r' or '\n' character, you have undefined
behavior. That can happen if the input is very long.
if (*line) history[hist_count++] = strdup(line);
It's worth being aware that strdup() is not specified by standard C
prior to C23. It is specified by POSIX. You're already relying on
nonstandard headers, so that's not really an issue.
[...]
I haven't read the entire program.
--
Keith Thompson (The_Other_Keith)
Keith.S.Thompson+u@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
--- PyGate Linux v1.5
* Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)