From 91e701881e693d6f5c36cb7e4cb0351309db4cec Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Sun, 30 Apr 2023 14:50:00 -0400 Subject: [PATCH] finish ed --- readme.md | 2 +- src/commands/ed.c | 275 ++++++++++++++++++++++++++++++++++++++++++---- src/util/shared.c | 2 +- 3 files changed, 255 insertions(+), 24 deletions(-) diff --git a/readme.md b/readme.md index 8308f19..2c02009 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ A terrible busybox/gnu coreutils clone. Currently the only supported commands are: -`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head` +`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed` ## How to diff --git a/src/commands/ed.c b/src/commands/ed.c index 2082ef6..7c55ce0 100644 --- a/src/commands/ed.c +++ b/src/commands/ed.c @@ -4,6 +4,7 @@ #include #include #include +#include #define INPUT_LEN 1024 @@ -12,11 +13,14 @@ static unsigned long line_capacity; static unsigned long line_count; static unsigned long line_current; static bool pending_writes; +static char* default_filename = NULL; +static re_t last_regex = NULL; enum LineAddressType { INDEX, RANGE, SET, + FREE }; struct LineAddress { @@ -64,6 +68,8 @@ static bool parse_regex(char** end, struct LineAddress* address, enum RegexDirec long int* buf = malloc(cap * sizeof(unsigned long)); re_t regex = re_compile(regex_str); + last_regex = regex; + unsigned long i = (dir == ALL ? 0 : line_current); unsigned long until = (dir == BEFORE ? 0 : line_count - 1); for (; (dir == BEFORE ? i >= until : i < until); dir == BEFORE ? i-- : i++) { @@ -81,6 +87,7 @@ static bool parse_regex(char** end, struct LineAddress* address, enum RegexDirec if (dir == BEFORE && i == 0) break; } + address->empty = false; address->type = SET; address->data.set.s = siz; address->data.set.c = cap; @@ -207,19 +214,26 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) static void free_address (struct LineAddress address) { if (address.type != SET) return; + address.type = FREE; free(address.data.set.b); } -static void free_data() { - if (lines == NULL) return; - for (unsigned long i = 0; i < line_count; i++) - free(lines[i]); - free(lines); - lines = NULL; +static void free_data(bool all) { + if (lines != NULL) { + for (unsigned long i = 0; i < line_count; i++) { + free(lines[i]); + } + free(lines); + lines = NULL; + } + if (all && default_filename != NULL) { + free(default_filename); + default_filename = NULL; + } } static void load_empty() { - free_data(); + free_data(false); line_capacity = 8; lines = malloc(sizeof(char*) * line_capacity); @@ -275,19 +289,20 @@ int ed_getline(char *buf, size_t size) { } static void load_file(FILE* file) { - free_data(); + free_data(false); line_current = 0; get_input(file, &lines, &line_capacity, &line_count); if (file != stdin) fclose(file); + } -static bool check_if_sure() { +static bool check_if_sure(char* prompt) { if (!pending_writes) { return true; } - printf("Do you really want to quit? "); + printf("%s", prompt); fflush(stdout); char buf[INPUT_LEN]; @@ -352,6 +367,7 @@ static bool handle_append(struct LineAddress* address) { get_input(stdin, &buf, &cap, &size); if (size > 0) { append_lines(address->data.index.i + 1, buf, size); + printf("ed: appened %lu lines\n", size); } line_current += size; free(buf); @@ -365,18 +381,143 @@ static bool handle_delete(struct LineAddress* address) { } if (address->type == INDEX) { delete_lines(address->data.index.i, address->data.index.i); + printf("ed: deleted line %ld\n", address->data.index.i+1); } else if (address->type == RANGE) { delete_lines(address->data.range.a, address->data.range.b); + printf("ed: deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1); } else if (address->type == SET) { for (unsigned long i = 0; i < address->data.set.s; i++) { delete_lines(address->data.set.b[i], address->data.set.b[i]); } + printf("ed: deleted %lu lines\n", address->data.set.s); } return true; } +static bool get_file_name(char** filename) { + size_t len = strlen(*filename); + if (len < 1 || (len == 1 && **filename == '\n')) { + if (default_filename == NULL) { + fprintf(stderr, "error: no default filename specified\n"); + return false; + } + *filename = default_filename; + } else { + if ((*filename)[len - 1] == '\n') { + (*filename)[len - 1] = '\0'; + len--; + } + if (default_filename != NULL) { + default_filename = realloc(default_filename, len + 1); + } else { + default_filename = malloc(len + 1); + } + memcpy(default_filename, *filename, len + 1); + *filename = default_filename; + } + return true; +} + +static void write_file(char* filename, struct LineAddress* address, char* type) { + if (line_count < 1) { + fprintf(stderr, "error: cannot write empty file\n"); + return; + } + if (!get_file_name(&filename)) return; + FILE* file = get_file_s(filename, type); + if (file == NULL) return; + int wrote = 0; + if (address->empty) { + for (unsigned long i = 0; i < line_count; i++) { + fprintf(file, "%s", lines[i]); + wrote++; + } + } else if (address->type == INDEX) { + long int i = address->data.index.i; + fprintf(file, "%s", lines[i]); + wrote++; + } else if (address->type == RANGE) { + for (long int i = address->data.range.a; i < address->data.range.b; i++) { + fprintf(file, "%s", lines[i]); + wrote++; + } + } else if (address->type == SET) { + for (unsigned long i = 0; i < address->data.set.s; i++) { + fprintf(file, "%s", lines[address->data.set.b[i]]); + wrote++; + } + } + pending_writes = false; + fclose(file); + printf("ed: wrote %d lines from %s\n", wrote, filename); +} + +static void read_file(char* filename) { + if (!get_file_name(&filename)) return; + FILE* file = get_file_s(filename, "r"); + if (file == NULL) return; + + unsigned long capacty = 8; + unsigned long size = 0; + char** buf = malloc(capacty * sizeof(char*)); + get_input(file, &buf, &capacty, &size); + + if (size < 1) { + free(buf); + fprintf(stderr, "error: attempted to read a empty file\n"); + return; + } + + long int line = -1; + if (line_count > 0) { + line = line_count - 1; + } + append_lines(line, buf, size); + free(buf); + printf("ed: read and appended %lu lines from %s\n", size, filename); +} + +static void expand_string(char** buf, int* capacity, int* size, char* text, int len) { + if (*size + len >= *capacity) { + *capacity *= 2; + if (*capacity < *size + len) *capacity = *size + len; + *buf = realloc(*buf, *capacity); + } + memcpy(*buf + *size, text, len); + *size += len; +} + +static int substute_string(long int index, long int matches, re_t regex, char* sub, int sub_len) { + int capacity = 8; + int size = 0; + char* buf = malloc(sizeof(char) * capacity); + + int offset = 0; + int matches_found = 0; + while(true) { + if (lines[index][offset] == '\0') break; + if (matches_found >= matches && matches > 0) break; + int distance, len; + if ((distance = re_matchp(regex, &lines[index][offset], &len)) == -1) { + break; + } + if (distance > 0) + expand_string(&buf, &capacity, &size, &lines[index][offset], distance); + expand_string(&buf, &capacity, &size, sub, sub_len); + offset += len + distance; + matches_found++; + } + + long int left = strlen(lines[index] + offset); + expand_string(&buf, &capacity, &size, &lines[index][offset], left + 1); + + free(lines[index]); + lines[index] = buf; + return matches_found; +} + static void prompt() { - printf("%ld: ", line_current + 1); + printf(": "); fflush(stdout); char buf[INPUT_LEN]; @@ -422,9 +563,11 @@ static void prompt() { return; } + bool linenumbers = false; +test: switch (cmd) { - case '\n': case '\0': + case '\n': if (address.empty) { if (line_current == line_count) { fprintf(stderr, "error: line number %ld does not exist\n", line_current + 1); @@ -460,45 +603,132 @@ static void prompt() { address.data.index.i = line_current - 1; handle_append(&address); break; + case 'n': + linenumbers = true; + __attribute__((fallthrough)); case 'p': if (address.empty && address.data.index.i >= (long int) line_count) { fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); break; } if (address.type == INDEX) { + if (linenumbers) printf("%ld\t", address.data.index.i + 1); printf("%s", lines[address.data.index.i]); } else if (address.type == RANGE) { for (long int i = address.data.range.a; i <= address.data.range.b; i++) { + if (linenumbers) printf("%ld\t", i + 1); printf("%s", lines[i]); } } else if (address.type == SET) { for (unsigned long i = 0; i < address.data.set.s; i++) { + if (linenumbers) printf("%ld\t", i +1); printf("%s", lines[address.data.set.b[i]]); } } break; case 'q': - if(check_if_sure()) { - free_address(address); - free_data(); - exit(EXIT_SUCCESS); - }; - break; + if (!check_if_sure("Quit for sure? ")) break; + __attribute__((fallthrough)); case 'Q': free_address(address); - free_data(); + free_data(true); exit(EXIT_SUCCESS); + break; case 'g': skip_whitespace(&index); free_address(address); if (*(index++) != '/') { - fprintf(stderr, "error: unexpected character at start or regex\n"); + fprintf(stderr, "error: unexpected character at start of regex\n"); break; } if (!parse_regex(&index, &address, ALL)) { return; } - for (unsigned long i = 0; i < address.data.set.s; i++) { - printf("%s", lines[address.data.set.b[i]]); + skip_whitespace(&index); + if (*index == '\n' || *index == '\0') { + cmd = 'p'; + } else { + cmd = *(index++); } + goto test; + break; + case 's': + skip_whitespace(&index); + free_address(address); + if (*(index++) != '/') { + fprintf(stderr, "error: unexpected character at start of regex\n"); + break; + } + if (!parse_regex(&index, &address, ALL)) { return; } + char* replace = index; + while(*index != '\0' && *index != '/') index++; + if (*index != '/') { + fprintf(stderr, "error: / missing after %c\n", *index); + break; + } + if (address.data.set.s < 1) { + printf("ed: no matches found\n"); + break; + } + *(index++) = '\0'; + long int matches; + if (*index == '\0') { + matches = 1; + } else if (*index == 'g') { + matches = -1; + } else { + char* end; + matches = strtol(index, &end, 10); + if (end == index) { + fprintf(stderr, "error: invalid number: %s\n", index); + break; + } + } + long int sub_len = strlen(replace); + long int matches_found = 0; + + for (unsigned long i = 0; i < address.data.set.s; i++) { + matches_found += substute_string(address.data.set.b[i], matches, last_regex, replace, sub_len); + } + printf("ed: replaced %ld matches over %ld lines\n", matches_found, address.data.set.s); + pending_writes = true; + break; + case 'w': { + bool quit = false; + if (*index == 'q') { + index++; + quit = true; + } + skip_whitespace(&index); + write_file(index, &address, "w"); + if (quit) { + free_address(address); + free_data(true); + exit(EXIT_SUCCESS); + } + break; + } + case 'r': { + skip_whitespace(&index); + read_file(index); + break; + } + case 'e': + if (!check_if_sure("Load new file for sure? ")) break; + __attribute__((fallthrough)); + case 'E': { + skip_whitespace(&index); + char* filename = index; + if(!get_file_name(&filename)) break; + FILE* file = get_file_s(filename, "r"); + if (file == NULL) break; + load_file(file); + break; + } + case 'W': + skip_whitespace(&index); + write_file(index, &address, "a"); + break; + case '=': + printf("%ld\n", line_current + 1); break; default: fprintf(stderr, "error: unimplemented command\n"); @@ -522,6 +752,7 @@ COMMAND(ed) { } else { FILE* file = get_file(argv[0], "r"); load_file(file); + get_file_name(&argv[0]); prompt_loop(); } return EXIT_SUCCESS; diff --git a/src/util/shared.c b/src/util/shared.c index c5c22e9..d512972 100644 --- a/src/util/shared.c +++ b/src/util/shared.c @@ -32,7 +32,7 @@ FILE* get_file_s(const char* path, const char* type) { read: file = fopen(path, type); if (file == NULL) { - fprintf(stderr, "error: failed to open file %s: %s\n", path, strerror(errno)); + fprintf(stderr, "error: failed to %s file %s: %s\n", type[0] == 'r' ? "read" : "write", path, strerror(errno)); } return file; }