diff --git a/Makefile b/Makefile index cf031b0..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) - mkfs/mkfs fs.img README.md $(UPROGS) +#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/docs/wc.md b/docs/wc.md new file mode 100644 index 0000000..38ea6e3 --- /dev/null +++ b/docs/wc.md @@ -0,0 +1,57 @@ +# 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: + +- `-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) + +If multiple flags are given, each selected count is displayed with a label. + +### Examples + +```sh +$ 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 longline.txt +Max line length: 301 +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. **The tests ensure correctness for lines, words, characters, bytes, and max line length.** + +### 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 + ``` + +4. If all tests pass, your implementation is diff --git a/empty.txt b/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/hello.txt b/hello.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/hello.txt @@ -0,0 +1 @@ +hello 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/longline.txt b/longline.txt new file mode 100644 index 0000000..768dcfd --- /dev/null +++ b/longline.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/multi.txt b/multi.txt new file mode 100644 index 0000000..7e6e08d --- /dev/null +++ b/multi.txt @@ -0,0 +1,2 @@ +one two +three 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/tabsnl.txt b/tabsnl.txt new file mode 100644 index 0000000..24ca110 --- /dev/null +++ b/tabsnl.txt @@ -0,0 +1,2 @@ +a b c + 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..6a7ca7e 100644 --- a/user/wc.c +++ b/user/wc.c @@ -1,54 +1,103 @@ -#include "kernel/types.h" -#include "kernel/stat.h" -#include "kernel/fcntl.h" -#include "user/user.h" +#include "types.h" +#include "user.h" -char buf[512]; +#define BUF_SIZE 512 -void -wc(int fd, char *name) -{ - int i, n; - int l, w, c, inword; +int is_word_char(char c) { + return !(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); +} + +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, words = 0, chars = 0, bytes = 0; + int max_line_len = 0, current_line_len = 0, inword = 0; + char buf[BUF_SIZE]; + int n; - l = w = c = 0; - inword = 0; - while((n = read(fd, buf, sizeof(buf))) > 0){ - for(i=0; i 0) { + bytes += n; + for (int i = 0; i < n; i++) { + char c = buf[i]; + chars++; + current_line_len++; + if (c == '\n') { + lines++; + if (current_line_len > max_line_len) + max_line_len = current_line_len; + current_line_len = 0; + } + if (is_word_char(c)) { + if (!inword) { + words++; + inword = 1; + } + } else { inword = 0; - else if(!inword){ - w++; - inword = 1; } } } - if(n < 0){ + // Check for last line without newline + if (current_line_len > 0 && current_line_len > max_line_len) + max_line_len = current_line_len; + + if (n < 0) { printf("wc: read error\n"); exit(1); } - printf("%d %d %d %s\n", l, w, c, name); -} -int -main(int argc, char *argv[]) -{ - int fd, i; + int flag_used = show_lines || show_words || show_chars || show_bytes || show_max_line; + if (flag_used) { + 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 { + 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); + } +} - if(argc <= 1){ - wc(0, ""); +int main(int argc, char *argv[]) { + int show_lines = 0, show_words = 0, show_chars = 0, show_bytes = 0, show_max_line = 0, file_start = 1; + int i; + 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; + else if (argv[i][j] == 'b') show_bytes = 1; + else if (argv[i][j] == 'L') show_max_line = 1; + } + file_start++; + } + } + if (argc == file_start) { + wc_stats(0, "", show_lines, show_words, show_chars, show_bytes, show_max_line); 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, show_bytes, show_max_line); close(fd); } exit(0); diff --git a/user/wc_test.c b/user/wc_test.c new file mode 100644 index 0000000..94176a0 --- /dev/null +++ b/user/wc_test.c @@ -0,0 +1,149 @@ +#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[] = { + { "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]); + + 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); + } + + printf("wc_test summary: %d pass, %d fail\n", pass, fail); + exit(fail ? 1 : 0); +} diff --git a/words.txt b/words.txt new file mode 100644 index 0000000..3774da6 --- /dev/null +++ b/words.txt @@ -0,0 +1 @@ +a b c