diff --git a/.hsh_history b/.hsh_history new file mode 100644 index 0000000..e69de29 diff --git a/src/.clang-format b/src/.clang-format new file mode 120000 index 0000000..f4af811 --- /dev/null +++ b/src/.clang-format @@ -0,0 +1 @@ +.clang-format \ No newline at end of file diff --git a/src/history.c b/src/history.c new file mode 100644 index 0000000..d2909fd --- /dev/null +++ b/src/history.c @@ -0,0 +1,83 @@ +#include +#include "history.h" +#define JUAN_IMPLEMENTATION +#include "juan.h" + +command_history *history_init(const char *path) +{ + if (create_file_if_not_exists(path) < 0) { + J_log(J_ERROR, "creat history file"); + return NULL; + } + + long file_size = size_of_file(path); + if (file_size < 0) { + J_log(J_ERROR, "get history file size"); + return NULL; + } + + struct Vec *array = init_vec_with_cap(HIST_MAX_LEN); + if (!array) + return NULL; + + if (file_size > 0) { + void *buf = malloc(file_size + 1); + + if (read_file_to_buffer(path, buf, file_size + 1) < 0) { + J_log(J_ERROR, "read history to buf"); + return NULL; + } + + split_newline_to_vec(buf, array); + } + + command_history *h = malloc(sizeof(command_history)); + h->commands = array; + h->path = path; + h->start = 0; + h->current = (array->len == 0) ? 0 : array->len--; + h->max_len = HIST_MAX_LEN; + + return h; +} + +void history_push(command_history *__restrict__ h, const char *command) +{ + if (h->commands->len == h->max_len) { + vec_insert_at(h->commands, (void *)command, h->start); + h->current = h->start; + h->start++; + + } else { + vec_push(h->commands, (void *)command); + h->current++; + } +} + +unsigned int history_next_offset(command_history *h) +{ + if (!h) + return -1; + unsigned int offset = h->current % h->commands->len; + h->current++; + return offset; +} + +unsigned int history_prev_offset(command_history *h) +{ + if (!h) + return -1; + unsigned int offset = h->current % h->commands->len; + h->current--; + return offset; +} + +const char *history_next(command_history *h) +{ + return h->commands->items[history_next_offset(h)]; +} + +const char *history_prev(command_history *h) +{ + return h->commands->items[history_prev_offset(h)]; +} diff --git a/src/history.h b/src/history.h new file mode 100644 index 0000000..526dc2b --- /dev/null +++ b/src/history.h @@ -0,0 +1,24 @@ +#ifndef HISTORY_H +#define HISTORY_H + +#include + +#define HIST_MAX_LEN 1000 + +typedef struct { + size_t start; + size_t current; + struct Vec *commands; // List of pointers to commands strings + const char *path; + size_t max_len; +} command_history; + +// read past commands from disk +command_history *history_init(const char *path); +void history_push(command_history *__restrict__ h, const char *command); +unsigned int history_next_offset(command_history *h); +unsigned int history_prev_offset(command_history *h); +const char *history_next(command_history *h); +const char *history_prev(command_history *h); + +#endif diff --git a/src/juan.h b/src/juan.h new file mode 100644 index 0000000..9810976 --- /dev/null +++ b/src/juan.h @@ -0,0 +1,282 @@ +// ISC License +// +// Copyright 2025 Juan Milkah +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DAMAGE ARISING +// OUT OF OR IN CONNECTION WITH THE SOFTWARE. + +#ifndef JUAN_H +#define JUAN_H + +#include +#include + +// Initial capacity for Vec dynamic array +#define INIT_VEC_CAP 64 + +// File IO operations +// Return 0 on success, -1 on failure +int create_file_if_not_exists(const char *path); +int file_exists(const char *path); // 0 if not exists, 1 if exists, -1 error +long size_of_file(const char *path); // -1 on error +int read_file_to_buffer(const char *path, void *buf, + size_t file_size); // 0 success, -1 error +int write_buffer_to_file(const char *path, void *buf, + size_t buf_size); // 0 success, -1 error +int append_buffer_to_file(const char *path, void *buf, + size_t buf_size); // 0 success, -1 error + +// Dynamic Data structures +struct Vec { + size_t cap; + size_t len; + void **items; +}; + +struct Vec *init_vec(void); +struct Vec *init_vec_with_cap(unsigned int cap); +int vec_insert_at(struct Vec *vec, void *item, + size_t index); // 0 success, -1 error +int vec_push(struct Vec *vec, void *item); // 0 success, -1 error +int realloc_vec(struct Vec *vec); // 0 success, -1 error +void drop_vec(struct Vec *vec); + +// Logging levels +enum Level { + J_ERROR, + J_WARN, + J_INFO, + J_DEBUG, +}; + +const char *log_level_to_string(enum Level l); +void exit_with_error(const char *message); +void J_log(enum Level l, const char *message); + +// String utilities +void split_newline_to_vec(char *s, struct Vec *v); +void split_at_delimiter_to_vec(char *s, struct Vec *v, char delimiter); + +#ifdef JUAN_IMPLEMENTATION + +#include "juan.h" +#include +#include +#include + +long size_of_file(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) { + return -1; + } + + if (fseek(f, 0, SEEK_END) < 0) { + fclose(f); + return -1; + } + + long size = ftell(f); + fclose(f); + if (size < 0) { + return -1; + } + return size; +} + +int read_file_to_buffer(const char *path, void *buf, size_t file_size) { + FILE *f = fopen(path, "r"); + if (!f) { + return -1; + } + + size_t read = fread(buf, 1, file_size, f); + fclose(f); + return (read == file_size) ? 0 : -1; +} + +int write_buffer_to_file(const char *path, void *buf, size_t buf_size) { + FILE *f = fopen(path, "w"); + if (!f) { + return -1; + } + + size_t written = fwrite(buf, 1, buf_size, f); + fclose(f); + return (written == buf_size) ? 0 : -1; +} + +int append_buffer_to_file(const char *path, void *buf, size_t buf_size) { + FILE *f = fopen(path, "a"); + if (!f) { + return -1; + } + + size_t written = fwrite(buf, 1, buf_size, f); + fclose(f); + return (written == buf_size) ? 0 : -1; +} + +int create_file_if_not_exists(const char *path) { + FILE *f = fopen(path, "r"); + if (f) { + fclose(f); + return 0; // File exists, treat as success/no error. + } + + f = fopen(path, "w"); + if (!f) { + return -1; + } + + fclose(f); + return 0; // File created successfully +} + +int file_exists(const char *path) { + FILE *f = fopen(path, "r"); + if (f) { + fclose(f); + return 1; // Exists + } + + if (errno == ENOENT) { + return 0; // Doesn't exist + } else { + return -1; // Error in checking + } +} + +struct Vec *init_vec(void) { return init_vec_with_cap(INIT_VEC_CAP); } + +struct Vec *init_vec_with_cap(unsigned int cap) { + if (cap == 0) { + cap = INIT_VEC_CAP; + } + void **items = malloc(cap * sizeof(void *)); + if (!items) + return NULL; + + struct Vec *v = malloc(sizeof(struct Vec)); + if (!v) { + free(items); + return NULL; + } + + v->cap = cap; + v->items = items; + v->len = 0; + return v; +} + +int realloc_vec(struct Vec *vec) { + size_t new_cap = vec->cap * 2; + void **new_items = reallocarray(vec->items, new_cap, sizeof(void *)); + if (!new_items) { + return -1; + } + vec->items = new_items; + vec->cap = new_cap; + return 0; +} + +int vec_insert_at(struct Vec *vec, void *item, size_t index) { + if (!vec) + return -1; + + if (index < vec->cap) { + char *item_copy = strdup((const char *)item); + if (!item_copy) + return -1; + vec->items[index] = item_copy; + return 0; + } + + if (vec->len == vec->cap) { + if (realloc_vec(vec) < 0) { + return -1; + } + } + + char *item_copy = strdup((const char *)item); + if (!item_copy) + return -1; + vec->items[vec->len++] = item_copy; + return 0; +} + +int vec_push(struct Vec *vec, void *item) { + int result = vec_insert_at(vec, item, vec->len); + if (!result) { + vec->len++; + } + + return result; +} + +void drop_vec(struct Vec *vec) { + if (!vec) + return; + for (size_t i = 0; i < vec->len; i++) { + free(vec->items[i]); + } + free(vec->items); + free(vec); +} + +const char *log_level_to_string(enum Level l) { + switch (l) { + case J_ERROR: + return "ERROR"; + case J_WARN: + return "WARN"; + case J_INFO: + return "INFO"; + case J_DEBUG: + return "DEBUG"; + default: + return "UNKNOWN"; + } +} + +void J_log(enum Level l, const char *message) { + const char *level_str = log_level_to_string(l); + fprintf(stderr, "%s: %s\n", level_str, message); +} + +void exit_with_error(const char *message) { + perror(message); + exit(EXIT_FAILURE); +} + +void split_at_delimiter_to_vec(char *s, struct Vec *v, char delimiter) { + if (!s || !v) + return; + char *start = s; + while (*start) { + char *delim_pos = strchr(start, delimiter); + size_t len = + delim_pos ? (size_t)(delim_pos - start) : strlen(start); + char *part = malloc(len + 1); + if (!part) + return; + memcpy(part, start, len); + part[len] = '\0'; + vec_push(v, part); + if (!delim_pos) + break; + start = delim_pos + 1; + } +} + +void split_newline_to_vec(char *s, struct Vec *v) { + split_at_delimiter_to_vec(s, v, '\n'); +} + +#endif +#endif diff --git a/src/main.c b/src/main.c index 8c84665..7becfc4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,11 @@ +#include "history.h" #include "shell.h" #include +#include #include +#define HIST_PATH ".hsh_history" + int main(int argc, char **argv) { if (argc > 2) { @@ -12,13 +16,24 @@ int main(int argc, char **argv) char *name = argc == 1 ? argv[0] : argv[1]; bool is_interactive = (argc == 1 && isatty(STDIN_FILENO)); - ShellState *shell = shell_init(name, is_interactive); + ShellState *shell = shell_init(name, is_interactive, HIST_PATH); if (!shell) { - fprintf(stderr, "Error: malloc failed\n"); + fprintf(stderr, "ERROR: Shell Init failed\n"); return 127; } + const char *commands[] = { "Hello", "mike", "angelo" }; + for (int i = 0; i < 3; i++) { + history_push(shell->history, commands[i]); + } + puts("Done push"); + + for (int i = 3; i > 0; i--) { + printf("%s\n", history_prev(shell->history)); + } + exit(0); + if (argc == 2) { FILE *stream = fopen(argv[1], "r"); if (!stream) { diff --git a/src/shell.c b/src/shell.c index dca6296..86af23a 100644 --- a/src/shell.c +++ b/src/shell.c @@ -1,6 +1,7 @@ #include "shell.h" #include "command.h" #include "executor.h" +#include "history.h" #include "lexer.h" #include "parser.h" #include "token.h" @@ -13,7 +14,7 @@ * * Return: Pointer to the initialized ShellState structure, or NULL on failure. */ -ShellState *shell_init(char *name, bool is_interactive) +ShellState *shell_init(char *name, bool is_interactive, const char *hist_path) { ShellState *shell = malloc(sizeof(ShellState)); if (!shell) @@ -24,6 +25,10 @@ ShellState *shell_init(char *name, bool is_interactive) shell->is_interactive_mode = is_interactive; shell->line_number = 0; shell->name = name; + command_history *hist = history_init(hist_path); + if (!hist) + return NULL; + shell->history = hist; return shell; } /** @@ -58,6 +63,8 @@ void shell_repl(ShellState *shell, FILE *stream) break; } + history_push(shell->history, line); + Token *tokens = tokenize(shell, line); free(line); line = NULL; diff --git a/src/shell.h b/src/shell.h index 6d56c9f..eb292be 100644 --- a/src/shell.h +++ b/src/shell.h @@ -3,6 +3,7 @@ #include #include +#include "history.h" typedef struct ShellState { bool fatal_error; @@ -10,9 +11,10 @@ typedef struct ShellState { bool had_error; char *name; int line_number; + command_history *history; } ShellState; -ShellState *shell_init(char *name, bool is_interactive); +ShellState *shell_init(char *name, bool is_interactive, const char *hist_path); void shell_free(ShellState *shell); void shell_repl(ShellState *shell, FILE *stream);