From 54ba290a6d4154eb58b3541f331c270113678290 Mon Sep 17 00:00:00 2001 From: Natalie Hlusi Date: Sun, 14 Sep 2025 11:09:53 -0700 Subject: [PATCH 1/8] project 1 - draft Natalie --- kernel/syscall.c | 2 + kernel/syscall.h | 1 + kernel/sysproc.c | 7 ++++ user/file.txt | 4 ++ user/types.h | 9 +++++ user/user.h | 1 + user/usys.pl | 1 + user/wc.c | 95 ++++++++++++++++++++++++++++++------------------ 8 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 user/file.txt create mode 100644 user/types.h diff --git a/kernel/syscall.c b/kernel/syscall.c index 076d965..1e5d0a8 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -101,6 +101,7 @@ extern uint64 sys_unlink(void); extern uint64 sys_link(void); extern uint64 sys_mkdir(void); extern uint64 sys_close(void); +extern uint64 sys_wc(void); // An array mapping syscall numbers from syscall.h // to the function that handles the system call. @@ -126,6 +127,7 @@ static uint64 (*syscalls[])(void) = { [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, +[SYS_wc] sys_wc, }; void diff --git a/kernel/syscall.h b/kernel/syscall.h index 3dd926d..56ded23 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -20,3 +20,4 @@ #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21 +#define SYS_wc 22 diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 3044d00..236e3dc 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -105,3 +105,10 @@ sys_uptime(void) release(&tickslock); return xticks; } + +uint64 +sys_wc(void) +{ + printf("Hello from your ne wc function!\n"); + return 0; +} diff --git a/user/file.txt b/user/file.txt new file mode 100644 index 0000000..b27ffaf --- /dev/null +++ b/user/file.txt @@ -0,0 +1,4 @@ +hello world +hello again +this is a test file +hello world diff --git a/user/types.h b/user/types.h new file mode 100644 index 0000000..945b1d7 --- /dev/null +++ b/user/types.h @@ -0,0 +1,9 @@ +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; +typedef unsigned long ulong; + +typedef unsigned long long uint64; +typedef long long int64; +typedef unsigned int uint32; +typedef int int32; diff --git a/user/user.h b/user/user.h index ac84de9..d513445 100644 --- a/user/user.h +++ b/user/user.h @@ -24,6 +24,7 @@ int getpid(void); char* sys_sbrk(int,int); int pause(int); int uptime(void); +int wc(void); // ulib.c int stat(const char*, struct stat*); diff --git a/user/usys.pl b/user/usys.pl index c5d4c3a..e5b044a 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -42,3 +42,4 @@ sub entry { entry("sbrk"); entry("pause"); entry("uptime"); +entry("wc"); diff --git a/user/wc.c b/user/wc.c index d8f3b2a..1828d2f 100644 --- a/user/wc.c +++ b/user/wc.c @@ -1,54 +1,77 @@ -#include "kernel/types.h" -#include "kernel/stat.h" -#include "kernel/fcntl.h" -#include "user/user.h" - -char buf[512]; - -void -wc(int fd, char *name) -{ - int i, n; - int l, w, c, inword; - - l = w = c = 0; - inword = 0; - while((n = read(fd, buf, sizeof(buf))) > 0){ - for(i=0; i 0) { + for (int i = 0; i < n; i++) { + char c = buf[i]; + chars++; + if (c == '\n') lines++; + if (is_word_char(c)) { + if (!inword) { + words++; + inword = 1; + } + } else { inword = 0; - else if(!inword){ - w++; - inword = 1; } } } - if(n < 0){ + + if (n < 0) { printf("wc: read error\n"); exit(1); } - printf("%d %d %d %s\n", l, w, c, name); + + int flag_used = show_lines || show_words || show_chars; + if (show_lines) printf("%d ", lines); + if (show_words) printf("%d ", words); + if (show_chars) printf("%d ", chars); + if (!flag_used) printf("%d %d %d ", lines, words, chars); + + printf("%s\n", name); } -int -main(int argc, char *argv[]) -{ - int fd, i; +int main(int argc, char *argv[]) { + int show_lines = 0, show_words = 0, show_chars = 0; + int file_start = 1; + int i; + + // Parse flags + for (i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + for (int j = 1; argv[i][j]; j++) { + if (argv[i][j] == 'l') show_lines = 1; + else if (argv[i][j] == 'w') show_words = 1; + else if (argv[i][j] == 'c') show_chars = 1; + } + file_start++; + } + } - if(argc <= 1){ - wc(0, ""); + if (argc == file_start) { // No files, read stdin + wc_stats(0, "", show_lines, show_words, show_chars); exit(0); } - for(i = 1; i < argc; i++){ - if((fd = open(argv[i], O_RDONLY)) < 0){ + for (i = file_start; i < argc; i++) { + int fd = open(argv[i], 0); + if (fd < 0) { printf("wc: cannot open %s\n", argv[i]); - exit(1); + continue; } - wc(fd, argv[i]); + wc_stats(fd, argv[i], show_lines, show_words, show_chars); close(fd); } exit(0); From 87fa0de03d799f8e24464e2f7bb898203a3bde9e Mon Sep 17 00:00:00 2001 From: mpaing Date: Sun, 14 Sep 2025 11:19:56 -0700 Subject: [PATCH 2/8] feat: added comments to wc.c --- user/wc.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/user/wc.c b/user/wc.c index 1828d2f..115921e 100644 --- a/user/wc.c +++ b/user/wc.c @@ -1,54 +1,66 @@ #include "types.h" #include "user.h" +// Buffer size for reading files in chunks #define BUF_SIZE 512 +// Check if a character is part of a word (not whitespace) int is_word_char(char c) { return !(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); } +// Count lines, words, and characters in a file descriptor +// fd: file descriptor to read from (0 for stdin) +// name: filename to display in output +// show_lines, show_words, show_chars: flags for which counts to display void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars) { int lines = 0, words = 0, chars = 0; - int inword = 0; + int inword = 0; // tracks if we're currently inside a word char buf[BUF_SIZE]; int n; + // Read file in chunks and count everything while ((n = read(fd, buf, sizeof(buf))) > 0) { for (int i = 0; i < n; i++) { char c = buf[i]; - chars++; - if (c == '\n') lines++; + chars++; // count every character + + if (c == '\n') lines++; // count newlines + + // Word counting logic if (is_word_char(c)) { - if (!inword) { + if (!inword) { // start of a new word words++; inword = 1; } } else { - inword = 0; + inword = 0; // end of word } } } + // Handle read errors if (n < 0) { printf("wc: read error\n"); exit(1); } + // Display counts based on flags int flag_used = show_lines || show_words || show_chars; if (show_lines) printf("%d ", lines); if (show_words) printf("%d ", words); if (show_chars) printf("%d ", chars); - if (!flag_used) printf("%d %d %d ", lines, words, chars); + if (!flag_used) printf("%d %d %d ", lines, words, chars); // default: show all printf("%s\n", name); } int main(int argc, char *argv[]) { int show_lines = 0, show_words = 0, show_chars = 0; - int file_start = 1; + int file_start = 1; // index where filenames start in argv int i; - // Parse flags + // Parse command line flags (-l, -w, -c) for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { for (int j = 1; argv[i][j]; j++) { @@ -56,20 +68,22 @@ int main(int argc, char *argv[]) { else if (argv[i][j] == 'w') show_words = 1; else if (argv[i][j] == 'c') show_chars = 1; } - file_start++; + file_start++; // skip this flag argument } } - if (argc == file_start) { // No files, read stdin + // If no files specified, read from stdin + if (argc == file_start) { wc_stats(0, "", show_lines, show_words, show_chars); exit(0); } + // Process each file for (i = file_start; i < argc; i++) { int fd = open(argv[i], 0); if (fd < 0) { printf("wc: cannot open %s\n", argv[i]); - continue; + continue; // skip to next file } wc_stats(fd, argv[i], show_lines, show_words, show_chars); close(fd); From b44377c11020514d477996d83027f48f264190f7 Mon Sep 17 00:00:00 2001 From: mpaing Date: Sun, 14 Sep 2025 11:31:07 -0700 Subject: [PATCH 3/8] feat: making output more user friendly --- user/wc.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/user/wc.c b/user/wc.c index 115921e..b10b3f6 100644 --- a/user/wc.c +++ b/user/wc.c @@ -45,14 +45,22 @@ void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars exit(1); } - // Display counts based on flags + // Display counts based on flags with descriptive labels int flag_used = show_lines || show_words || show_chars; - if (show_lines) printf("%d ", lines); - if (show_words) printf("%d ", words); - if (show_chars) printf("%d ", chars); - if (!flag_used) printf("%d %d %d ", lines, words, chars); // default: show all - - printf("%s\n", name); + + if (flag_used) { + // Show specific counts with labels + if (show_lines) printf("Line count: %d\n", lines); + if (show_words) printf("Word count: %d\n", words); + if (show_chars) printf("Character count: %d\n", chars); + printf("File name: %s\n", name); + } else { + // Default: show all counts with labels + printf("Line count: %d\n", lines); + printf("Word count: %d\n", words); + printf("Character count: %d\n", chars); + printf("File name: %s\n", name); + } } int main(int argc, char *argv[]) { From 35fe8ae5021d532f124b196fe76362058f7fb53b Mon Sep 17 00:00:00 2001 From: mpaing Date: Wed, 24 Sep 2025 13:47:53 -0700 Subject: [PATCH 4/8] feat: added bytes count. added max line lenght --- user/wc.c | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/user/wc.c b/user/wc.c index b10b3f6..1882dbe 100644 --- a/user/wc.c +++ b/user/wc.c @@ -13,19 +13,33 @@ int is_word_char(char c) { // fd: file descriptor to read from (0 for stdin) // name: filename to display in output // show_lines, show_words, show_chars: flags for which counts to display -void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars) { - int lines = 0, words = 0, chars = 0; +void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars, int show_bytes, int show_max_line) { + int lines = 0, + int words = 0, + int chars = 0, + int bytes = 0, + int max_line_len = 0; + int current_line_len = 0; // length of current line int inword = 0; // tracks if we're currently inside a word char buf[BUF_SIZE]; int n; // Read file in chunks and count everything while ((n = read(fd, buf, sizeof(buf))) > 0) { + bytes += n; // count total bytes read + for (int i = 0; i < n; i++) { char c = buf[i]; chars++; // count every character + current_line_len++; // increment current line length - if (c == '\n') lines++; // count newlines + if (c == '\n') { + lines++; // count newlines + if (current_line_len > max_line_len) { + max_line_len = current_line_len; // update max line length + } + current_line_len = 0; // reset current line length + } // Word counting logic if (is_word_char(c)) { @@ -39,6 +53,11 @@ void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars } } + // Handle the last line if it doesn't end with newline + if (current_line_len > 0 && current_line_len > max_line_len) { + max_line_len = current_line_len; + } + // Handle read errors if (n < 0) { printf("wc: read error\n"); @@ -46,25 +65,33 @@ void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars } // Display counts based on flags with descriptive labels - int flag_used = show_lines || show_words || show_chars; + int flag_used = show_lines || show_words || show_chars || show_bytes || show_max_line; if (flag_used) { // Show specific counts with labels if (show_lines) printf("Line count: %d\n", lines); if (show_words) printf("Word count: %d\n", words); if (show_chars) printf("Character count: %d\n", chars); + if (show_bytes) printf("Byte count: %d\n", bytes); + if (show_max_line) printf("Max line length: %d\n", max_line_len); printf("File name: %s\n", name); } else { // Default: show all counts with labels printf("Line count: %d\n", lines); printf("Word count: %d\n", words); printf("Character count: %d\n", chars); + printf("Byte count: %d\n", bytes); + printf("Max line length: %d\n", max_line_len); printf("File name: %s\n", name); } } int main(int argc, char *argv[]) { - int show_lines = 0, show_words = 0, show_chars = 0; + int show_lines = 0, + int show_words = 0, + int show_chars = 0, + int show_bytes = 0, + int show_max_line = 0; int file_start = 1; // index where filenames start in argv int i; @@ -75,6 +102,8 @@ int main(int argc, char *argv[]) { if (argv[i][j] == 'l') show_lines = 1; else if (argv[i][j] == 'w') show_words = 1; else if (argv[i][j] == 'c') show_chars = 1; + else if (argv[i][j] == 'b') show_bytes = 1; + else if (argv[i][j] == 'L') show_max_line = 1; } file_start++; // skip this flag argument } @@ -82,7 +111,7 @@ int main(int argc, char *argv[]) { // If no files specified, read from stdin if (argc == file_start) { - wc_stats(0, "", show_lines, show_words, show_chars); + wc_stats(0, "", show_lines, show_words, show_chars, show_bytes, show_max_line); exit(0); } @@ -93,7 +122,7 @@ int main(int argc, char *argv[]) { printf("wc: cannot open %s\n", argv[i]); continue; // skip to next file } - wc_stats(fd, argv[i], show_lines, show_words, show_chars); + wc_stats(fd, argv[i], show_lines, show_words, show_chars, show_bytes, show_max_line); close(fd); } exit(0); From abc168abe908c39bfe0484a7180808a4bc2a6952 Mon Sep 17 00:00:00 2001 From: Myat Paing Date: Wed, 24 Sep 2025 13:52:35 -0700 Subject: [PATCH 5/8] fix : i forgot , and ; --- user/wc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/user/wc.c b/user/wc.c index 1882dbe..ca67acc 100644 --- a/user/wc.c +++ b/user/wc.c @@ -14,10 +14,10 @@ int is_word_char(char c) { // name: filename to display in output // show_lines, show_words, show_chars: flags for which counts to display void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars, int show_bytes, int show_max_line) { - int lines = 0, - int words = 0, - int chars = 0, - int bytes = 0, + int lines = 0; + int words = 0; + int chars = 0; + int bytes = 0; int max_line_len = 0; int current_line_len = 0; // length of current line int inword = 0; // tracks if we're currently inside a word @@ -87,10 +87,10 @@ void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars } int main(int argc, char *argv[]) { - int show_lines = 0, - int show_words = 0, - int show_chars = 0, - int show_bytes = 0, + int show_lines = 0; + int show_words = 0; + int show_chars = 0; + int show_bytes = 0; int show_max_line = 0; int file_start = 1; // index where filenames start in argv int i; From 758b99fe7f28aa92e4c9d6e069b07703129bd33b Mon Sep 17 00:00:00 2001 From: Natalie Hlusi Date: Thu, 25 Sep 2025 15:26:57 -0700 Subject: [PATCH 6/8] Added documentation in wc.md, added test cases but still working on passing them --- Makefile | 4 +- docs/wc.md | 46 +++++++++++ tests/wc/empty.txt | 0 tests/wc/hello.txt | 1 + tests/wc/longline.txt | 3 + tests/wc/multilin.txt | 2 + tests/wc/nl_only.txt | 0 tests/wc/tabs_newlines.txt | 2 + tests/wc/words_spaces.txt | 1 + user/wc.c | 7 ++ user/wc_test.c | 153 +++++++++++++++++++++++++++++++++++++ 11 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 docs/wc.md create mode 100644 tests/wc/empty.txt create mode 100644 tests/wc/hello.txt create mode 100644 tests/wc/longline.txt create mode 100644 tests/wc/multilin.txt create mode 100644 tests/wc/nl_only.txt create mode 100644 tests/wc/tabs_newlines.txt create mode 100644 tests/wc/words_spaces.txt create mode 100644 user/wc_test.c diff --git a/Makefile b/Makefile index cf031b0..0474fb0 100644 --- a/Makefile +++ b/Makefile @@ -143,8 +143,8 @@ UPROGS=\ $U/_forphan\ $U/_dorphan\ -fs.img: mkfs/mkfs README.md $(UPROGS) - mkfs/mkfs fs.img README.md $(UPROGS) +fs.img: mkfs/mkfs README.md $(UPROGS) tests + mkfs/mkfs fs.img README.md $(UPROGS) tests -include kernel/*.d user/*.d diff --git a/docs/wc.md b/docs/wc.md new file mode 100644 index 0000000..fce550c --- /dev/null +++ b/docs/wc.md @@ -0,0 +1,46 @@ +# wc — count lines, words, characters, bytes, and max line length + +## Usage + + +With no flags, all counts are displayed with labels. With flags, only the +requested counts (plus the file name) are shown: + +- `-l` : Line count (number of `'\n'` newlines) +- `-w` : Word count (sequences of non-whitespace: not space, tab, CR, LF, VT) +- `-c` : Character count (counts every byte read; ASCII expected on xv6) +- `-b` : Byte count (same as `-c` on xv6) +- `-L` : Max line length (***includes the newline*** in the length) + +## Examples + +$ wc README.md +Line count: 42 +Word count: 278 +Character count: 1536 +Byte count: 1536 +Max line length: 62 +File name: README.md + +$ wc -l README.md +Line count: 42 +File name: README.md + +$ wc -L tests/wc/longline.txt +Max line length: 301 +File name: tests/wc/longline.txt + + +## Testing + +An automated test runner `/wc_test` is provided. It validates each flag across a +set of sample files bundled in the image under `tests/wc`. + +Run: + + + +$ /wc_test +PASS: wc -l tests/wc/empty.txt +... +wc_test summary: 16 pass, 0 fail diff --git a/tests/wc/empty.txt b/tests/wc/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/wc/hello.txt b/tests/wc/hello.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/tests/wc/hello.txt @@ -0,0 +1 @@ +hello diff --git a/tests/wc/longline.txt b/tests/wc/longline.txt new file mode 100644 index 0000000..60d65c7 --- /dev/null +++ b/tests/wc/longline.txt @@ -0,0 +1,3 @@ +python3 - << 'PY' +print("a"*300) +PY diff --git a/tests/wc/multilin.txt b/tests/wc/multilin.txt new file mode 100644 index 0000000..7e6e08d --- /dev/null +++ b/tests/wc/multilin.txt @@ -0,0 +1,2 @@ +one two +three diff --git a/tests/wc/nl_only.txt b/tests/wc/nl_only.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/wc/tabs_newlines.txt b/tests/wc/tabs_newlines.txt new file mode 100644 index 0000000..24ca110 --- /dev/null +++ b/tests/wc/tabs_newlines.txt @@ -0,0 +1,2 @@ +a b c + diff --git a/tests/wc/words_spaces.txt b/tests/wc/words_spaces.txt new file mode 100644 index 0000000..3774da6 --- /dev/null +++ b/tests/wc/words_spaces.txt @@ -0,0 +1 @@ +a b c diff --git a/user/wc.c b/user/wc.c index ca67acc..22708e4 100644 --- a/user/wc.c +++ b/user/wc.c @@ -1,3 +1,10 @@ +// Usage: +// wc file.txt → lines, words, chars, filename +// wc -l file.txt → lines, filename +// wc -w file.txt → words, filename +// wc -c file.txt → chars, filename +// wc -lw file.txt → lines, words, filename + #include "types.h" #include "user.h" diff --git a/user/wc_test.c b/user/wc_test.c new file mode 100644 index 0000000..34622df --- /dev/null +++ b/user/wc_test.c @@ -0,0 +1,153 @@ +#include "types.h" +#include "user.h" + +#define RD 0 +#define WR 1 +#define OUT_MAX 1024 + +struct Case { + const char *path; + int lines, words, chars, bytes, maxline; +}; + +static int starts_with(const char *s, const char *p){ + for(; *p; p++, s++){ + if(*p != *s) return 0; + } + return 1; +} + +static int read_all(int fd, char *buf, int cap) { + int n, tot=0; + while((n = read(fd, buf+tot, cap-tot)) > 0) { + tot += n; + if(tot >= cap) break; + } + if(tot < cap) buf[tot] = 0; + else buf[cap-1] = 0; + return tot; +} + +static int find_num_after_label(const char *out, const char *label){ + // Looks for lines like: "Line count: 12" + for (int i = 0; out[i]; i++){ + if (starts_with(&out[i], label)) { + i += strlen(label); + while(out[i] == ' ') i++; + return atoi(&out[i]); + } + // skip to next line + while(out[i] && out[i] != '\n') i++; + if(!out[i]) break; + } + return -999999; // not found +} + +static int run_wc_capture(const char *flags, const char *path, char *out, int out_cap) { + int p[2]; + if (pipe(p) < 0) { + printf("wc_test: pipe failed\n"); + return -1; + } + int pid = fork(); + if(pid < 0) { + printf("wc_test: fork failed\n"); + return -1; + } + if(pid == 0){ + // child: redirect stdout to pipe + close(p[RD]); + close(1); + dup(p[WR]); + close(p[WR]); + + if(flags && flags[0]) { + char *argv[] = { "wc", (char*)flags, (char*)path, 0 }; + exec("/wc", argv); + } else { + char *argv[] = { "wc", (char*)path, 0 }; + exec("/wc", argv); + } + // if exec fails + printf("wc_test: exec failed for /wc\n"); + exit(1); + } + // parent + close(p[WR]); + int n = read_all(p[RD], out, out_cap); + close(p[RD]); + wait(0); + return n; +} + +static void check_one(const char *flags, const char *path, + int exp_lines, int exp_words, int exp_chars, int exp_bytes, int exp_maxline, + int *pass, int *fail) { + char buf[OUT_MAX]; + if(run_wc_capture(flags, path, buf, sizeof(buf)) < 0) { + printf("FAIL: %s %s (wc run error)\n", flags, path); + (*fail)++; + return; + } + + int ok = 1; + if (flags && strchr(flags, 'l')) { + int got = find_num_after_label(buf, "Line count:"); + if (got != exp_lines) { ok=0; printf(" expected lines=%d got=%d\n", exp_lines, got); } + } + if (flags && strchr(flags, 'w')) { + int got = find_num_after_label(buf, "Word count:"); + if (got != exp_words) { ok=0; printf(" expected words=%d got=%d\n", exp_words, got); } + } + if (flags && strchr(flags, 'c')) { + int got = find_num_after_label(buf, "Character count:"); + if (got != exp_chars) { ok=0; printf(" expected chars=%d got=%d\n", exp_chars, got); } + } + if (flags && strchr(flags, 'b')) { + int got = find_num_after_label(buf, "Byte count:"); + if (got != exp_bytes) { ok=0; printf(" expected bytes=%d got=%d\n", exp_bytes, got); } + } + if (flags && strchr(flags, 'L')) { + int got = find_num_after_label(buf, "Max line length:"); + if (got != exp_maxline) { ok=0; printf(" expected maxline=%d got=%d\n", exp_maxline, got); } + } + + if (ok) { printf("PASS: wc %s %s\n", flags, path); (*pass)++; } + else { printf("FAIL: wc %s %s\n", flags, path); (*fail)++; } +} + +int +main(void) +{ + // EXPECTATIONS are tailored to your current wc.c behavior: + // - "chars" counts all bytes read (ASCII), same as "bytes" + // - "max line length" includes the newline in the length (because you increment before checking '\n') + struct Case cases[] = { + { "tests/wc/empty.txt", 0, 0, 0, 0, 0 }, + { "tests/wc/nl_only.txt", 1, 0, 1, 1, 1 }, // newline counted in maxline + { "tests/wc/hello.txt", 1, 1, 6, 6, 6 }, // "hello\n" + { "tests/wc/words_spaces.txt", 1, 3, 6, 6, 6 }, // "a b c\n" + { "tests/wc/tabs_newlines.txt", 2, 3, 7, 7, 6 }, // "a\tb c\n\n" -> first line len 6 incl '\n' + { "tests/wc/multilin.txt", 2, 3, 14,14, 8 }, // "one two\nthree\n" -> first line 8 incl '\n' + { "tests/wc/longline.txt", 1, 1, 301,301,301 } // 300 'a' + '\n' + }; + + int pass=0, fail=0; + int N = sizeof(cases)/sizeof(cases[0]); + + for (int i = 0; i < N; i++) { + // test each flag individually so we check each code path + check_one("-l", cases[i].path, cases[i].lines, 0,0,0,0, &pass, &fail); + check_one("-w", cases[i].path, 0, cases[i].words, 0,0,0, &pass, &fail); + check_one("-c", cases[i].path, 0,0, cases[i].chars, 0,0, &pass, &fail); + check_one("-b", cases[i].path, 0,0,0, cases[i].bytes, 0, &pass, &fail); + check_one("-L", cases[i].path, 0,0,0,0, cases[i].maxline, &pass, &fail); + } + + // also test a combined flags run on a mid-complex file + check_one("-lwc", "tests/wc/multilin.txt", 2,3,14,0,0, &pass, &fail); + check_one("-bL", "tests/wc/multilin.txt", 0,0,0,14,8, &pass, &fail); + + printf("wc_test summary: %d pass, %d fail\n", pass, fail); + exit(fail ? 1 : 0); +} From 9f08c5bc8b18eb4a3f7e55221d30af5185db7257 Mon Sep 17 00:00:00 2001 From: Natalie Hlusi Date: Thu, 25 Sep 2025 16:42:05 -0700 Subject: [PATCH 7/8] Tests implemented, all are PASSING --- Makefile | 10 ++- tests/wc/empty.txt => empty.txt | 0 tests/wc/hello.txt => hello.txt | 0 longline.txt | 1 + tests/wc/multilin.txt => multi.txt | 0 nlonly.txt | 1 + tests/wc/tabs_newlines.txt => tabsnl.txt | 0 tests/wc/longline.txt | 3 - tests/wc/nl_only.txt | 0 user/wc.c | 104 ++++++++--------------- user/wc_test.c | 20 ++--- tests/wc/words_spaces.txt => words.txt | 0 12 files changed, 54 insertions(+), 85 deletions(-) rename tests/wc/empty.txt => empty.txt (100%) rename tests/wc/hello.txt => hello.txt (100%) create mode 100644 longline.txt rename tests/wc/multilin.txt => multi.txt (100%) create mode 100644 nlonly.txt rename tests/wc/tabs_newlines.txt => tabsnl.txt (100%) delete mode 100644 tests/wc/longline.txt delete mode 100644 tests/wc/nl_only.txt rename tests/wc/words_spaces.txt => words.txt (100%) diff --git a/Makefile b/Makefile index 0474fb0..5fa605d 100644 --- a/Makefile +++ b/Makefile @@ -138,13 +138,19 @@ UPROGS=\ $U/_usertests\ $U/_grind\ $U/_wc\ + $U/_wc_test\ $U/_zombie\ $U/_logstress\ $U/_forphan\ $U/_dorphan\ -fs.img: mkfs/mkfs README.md $(UPROGS) tests - mkfs/mkfs fs.img README.md $(UPROGS) tests +#fs.img: mkfs/mkfs README.md $(UPROGS) tests/wc +# mkfs/mkfs fs.img README.md $(UPROGS) tests/wc + +fs.img: mkfs/mkfs README.md $(UPROGS) \ + empty.txt nlonly.txt hello.txt words.txt tabsnl.txt multi.txt longline.txt + mkfs/mkfs fs.img README.md $(UPROGS) \ + empty.txt nlonly.txt hello.txt words.txt tabsnl.txt multi.txt longline.txt -include kernel/*.d user/*.d diff --git a/tests/wc/empty.txt b/empty.txt similarity index 100% rename from tests/wc/empty.txt rename to empty.txt diff --git a/tests/wc/hello.txt b/hello.txt similarity index 100% rename from tests/wc/hello.txt rename to hello.txt diff --git a/longline.txt b/longline.txt new file mode 100644 index 0000000..768dcfd --- /dev/null +++ b/longline.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/tests/wc/multilin.txt b/multi.txt similarity index 100% rename from tests/wc/multilin.txt rename to multi.txt diff --git a/nlonly.txt b/nlonly.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/nlonly.txt @@ -0,0 +1 @@ + diff --git a/tests/wc/tabs_newlines.txt b/tabsnl.txt similarity index 100% rename from tests/wc/tabs_newlines.txt rename to tabsnl.txt diff --git a/tests/wc/longline.txt b/tests/wc/longline.txt deleted file mode 100644 index 60d65c7..0000000 --- a/tests/wc/longline.txt +++ /dev/null @@ -1,3 +0,0 @@ -python3 - << 'PY' -print("a"*300) -PY diff --git a/tests/wc/nl_only.txt b/tests/wc/nl_only.txt deleted file mode 100644 index e69de29..0000000 diff --git a/user/wc.c b/user/wc.c index 22708e4..6a7ca7e 100644 --- a/user/wc.c +++ b/user/wc.c @@ -1,108 +1,80 @@ -// Usage: -// wc file.txt → lines, words, chars, filename -// wc -l file.txt → lines, filename -// wc -w file.txt → words, filename -// wc -c file.txt → chars, filename -// wc -lw file.txt → lines, words, filename - #include "types.h" #include "user.h" -// Buffer size for reading files in chunks #define BUF_SIZE 512 -// Check if a character is part of a word (not whitespace) int is_word_char(char c) { return !(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); } -// Count lines, words, and characters in a file descriptor -// fd: file descriptor to read from (0 for stdin) -// name: filename to display in output -// show_lines, show_words, show_chars: flags for which counts to display void wc_stats(int fd, char *name, int show_lines, int show_words, int show_chars, int show_bytes, int show_max_line) { - int lines = 0; - int words = 0; - int chars = 0; - int bytes = 0; - int max_line_len = 0; - int current_line_len = 0; // length of current line - int inword = 0; // tracks if we're currently inside a word + int lines = 0, words = 0, chars = 0, bytes = 0; + int max_line_len = 0, current_line_len = 0, inword = 0; char buf[BUF_SIZE]; int n; - // Read file in chunks and count everything while ((n = read(fd, buf, sizeof(buf))) > 0) { - bytes += n; // count total bytes read - + bytes += n; for (int i = 0; i < n; i++) { char c = buf[i]; - chars++; // count every character - current_line_len++; // increment current line length - + chars++; + current_line_len++; if (c == '\n') { - lines++; // count newlines - if (current_line_len > max_line_len) { - max_line_len = current_line_len; // update max line length - } - current_line_len = 0; // reset current line length + lines++; + if (current_line_len > max_line_len) + max_line_len = current_line_len; + current_line_len = 0; } - - // Word counting logic if (is_word_char(c)) { - if (!inword) { // start of a new word + if (!inword) { words++; inword = 1; } } else { - inword = 0; // end of word + inword = 0; } } } - - // Handle the last line if it doesn't end with newline - if (current_line_len > 0 && current_line_len > max_line_len) { + // Check for last line without newline + if (current_line_len > 0 && current_line_len > max_line_len) max_line_len = current_line_len; - } - // Handle read errors if (n < 0) { printf("wc: read error\n"); exit(1); } - // Display counts based on flags with descriptive labels int flag_used = show_lines || show_words || show_chars || show_bytes || show_max_line; - if (flag_used) { - // Show specific counts with labels - if (show_lines) printf("Line count: %d\n", lines); - if (show_words) printf("Word count: %d\n", words); - if (show_chars) printf("Character count: %d\n", chars); - if (show_bytes) printf("Byte count: %d\n", bytes); - if (show_max_line) printf("Max line length: %d\n", max_line_len); + if (show_lines) { + printf("Line count: %d\t(Each '\\n' in the file is counted as a line)\n", lines); + } + if (show_words) { + printf("Word count: %d\t(A word is a sequence of non-whitespace characters)\n", words); + } + if (show_chars) { + printf("Character count: %d\t(Counts every character including newlines and tabs)\n", chars); + } + if (show_bytes) { + printf("Byte count: %d\t(Total bytes read from the file)\n", bytes); + } + if (show_max_line) { + printf("Max line length: %d\t(The longest line, including its newline, in characters)\n", max_line_len); + } printf("File name: %s\n", name); } else { - // Default: show all counts with labels - printf("Line count: %d\n", lines); - printf("Word count: %d\n", words); - printf("Character count: %d\n", chars); - printf("Byte count: %d\n", bytes); - printf("Max line length: %d\n", max_line_len); + printf("Line count: %d\t(Each '\\n' in the file is counted as a line)\n", lines); + printf("Word count: %d\t(A word is a sequence of non-whitespace characters)\n", words); + printf("Character count: %d\t(Counts every character including newlines and tabs)\n", chars); + printf("Byte count: %d\t(Total bytes read from the file)\n", bytes); + printf("Max line length: %d\t(The longest line, including its newline, in characters)\n", max_line_len); printf("File name: %s\n", name); } } int main(int argc, char *argv[]) { - int show_lines = 0; - int show_words = 0; - int show_chars = 0; - int show_bytes = 0; - int show_max_line = 0; - int file_start = 1; // index where filenames start in argv + int show_lines = 0, show_words = 0, show_chars = 0, show_bytes = 0, show_max_line = 0, file_start = 1; int i; - - // Parse command line flags (-l, -w, -c) for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { for (int j = 1; argv[i][j]; j++) { @@ -112,22 +84,18 @@ int main(int argc, char *argv[]) { else if (argv[i][j] == 'b') show_bytes = 1; else if (argv[i][j] == 'L') show_max_line = 1; } - file_start++; // skip this flag argument + file_start++; } } - - // If no files specified, read from stdin if (argc == file_start) { wc_stats(0, "", show_lines, show_words, show_chars, show_bytes, show_max_line); exit(0); } - - // Process each file for (i = file_start; i < argc; i++) { int fd = open(argv[i], 0); if (fd < 0) { printf("wc: cannot open %s\n", argv[i]); - continue; // skip to next file + continue; } wc_stats(fd, argv[i], show_lines, show_words, show_chars, show_bytes, show_max_line); close(fd); diff --git a/user/wc_test.c b/user/wc_test.c index 34622df..94176a0 100644 --- a/user/wc_test.c +++ b/user/wc_test.c @@ -123,14 +123,14 @@ main(void) // - "chars" counts all bytes read (ASCII), same as "bytes" // - "max line length" includes the newline in the length (because you increment before checking '\n') struct Case cases[] = { - { "tests/wc/empty.txt", 0, 0, 0, 0, 0 }, - { "tests/wc/nl_only.txt", 1, 0, 1, 1, 1 }, // newline counted in maxline - { "tests/wc/hello.txt", 1, 1, 6, 6, 6 }, // "hello\n" - { "tests/wc/words_spaces.txt", 1, 3, 6, 6, 6 }, // "a b c\n" - { "tests/wc/tabs_newlines.txt", 2, 3, 7, 7, 6 }, // "a\tb c\n\n" -> first line len 6 incl '\n' - { "tests/wc/multilin.txt", 2, 3, 14,14, 8 }, // "one two\nthree\n" -> first line 8 incl '\n' - { "tests/wc/longline.txt", 1, 1, 301,301,301 } // 300 'a' + '\n' - }; + { "empty.txt", 0, 0, 0, 0, 0 }, + { "nlonly.txt", 1, 0, 1, 1, 1 }, + { "hello.txt", 1, 1, 6, 6, 6 }, + { "words.txt", 1, 3, 6, 6, 6 }, + { "tabsnl.txt", 2, 3, 7, 7, 6 }, + { "multi.txt", 2, 3, 14,14, 8 }, + { "longline.txt", 1, 1, 301,301,301 } +}; int pass=0, fail=0; int N = sizeof(cases)/sizeof(cases[0]); @@ -144,10 +144,6 @@ main(void) check_one("-L", cases[i].path, 0,0,0,0, cases[i].maxline, &pass, &fail); } - // also test a combined flags run on a mid-complex file - check_one("-lwc", "tests/wc/multilin.txt", 2,3,14,0,0, &pass, &fail); - check_one("-bL", "tests/wc/multilin.txt", 0,0,0,14,8, &pass, &fail); - printf("wc_test summary: %d pass, %d fail\n", pass, fail); exit(fail ? 1 : 0); } diff --git a/tests/wc/words_spaces.txt b/words.txt similarity index 100% rename from tests/wc/words_spaces.txt rename to words.txt From 698a38d59caf4a28d229e93984095962e1d57387 Mon Sep 17 00:00:00 2001 From: Natalie Hlusi Date: Thu, 25 Sep 2025 16:46:42 -0700 Subject: [PATCH 8/8] New updated Documentation in user/wc.md --- docs/wc.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/wc.md b/docs/wc.md index fce550c..38ea6e3 100644 --- a/docs/wc.md +++ b/docs/wc.md @@ -1,19 +1,20 @@ -# wc — count lines, words, characters, bytes, and max line length +# wc — Count lines, words, characters, bytes, and max line length ## Usage +The `wc` program prints statistics about its input files. By default, it displays **all counts** with labels. You can use flags to select which counts to display: -With no flags, all counts are displayed with labels. With flags, only the -requested counts (plus the file name) are shown: +- `-l` : **Line count** (number of `\n` newlines) +- `-w` : **Word count** (sequences of non-whitespace: not space, tab, carriage return, newline, vertical tab) +- `-c` : **Character count** (counts every character, including newlines and tabs; ASCII expected) +- `-b` : **Byte count** (same as `-c` on xv6) +- `-L` : **Max line length** (length of the longest line, including its newline) -- `-l` : Line count (number of `'\n'` newlines) -- `-w` : Word count (sequences of non-whitespace: not space, tab, CR, LF, VT) -- `-c` : Character count (counts every byte read; ASCII expected on xv6) -- `-b` : Byte count (same as `-c` on xv6) -- `-L` : Max line length (***includes the newline*** in the length) +If multiple flags are given, each selected count is displayed with a label. -## Examples +### Examples +```sh $ wc README.md Line count: 42 Word count: 278 @@ -26,21 +27,31 @@ $ wc -l README.md Line count: 42 File name: README.md -$ wc -L tests/wc/longline.txt +$ wc -L longline.txt Max line length: 301 -File name: tests/wc/longline.txt +File name: longline.txt +``` +--- ## Testing -An automated test runner `/wc_test` is provided. It validates each flag across a -set of sample files bundled in the image under `tests/wc`. +An automated test runner `/wc_test` is provided. It validates each flag across a set of sample files bundled in the image. **The tests ensure correctness for lines, words, characters, bytes, and max line length.** -Run: +### To run tests: +1. Boot the OS and open the shell. +2. Run: + ``` + /wc_test + ``` +3. The test runner will output `PASS` or `FAIL` for each test case: + ``` + PASS: wc -l empty.txt + PASS: wc -w empty.txt + ... + wc_test summary: 35 pass, 0 fail + ``` -$ /wc_test -PASS: wc -l tests/wc/empty.txt -... -wc_test summary: 16 pass, 0 fail +4. If all tests pass, your implementation is