aboutsummaryrefslogtreecommitdiffstats
path: root/usr/grep.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 16:02:55 +0200
committeruser <user@clank>2026-09-22 16:02:55 +0200
commit43e43b6dcec29e2b008d20135f5d02523b62fbcf (patch)
treed647acd07596e290c70a9884c1dc5d8c20a31067 /usr/grep.c
parentnet: three TCP receive bugs - caller corruption, data loss, early EOF (diff)
downloadgbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.tar.gz
gbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.tar.xz
gbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.zip
usr: grep, more, cp, nc - and make pollcon pump the link
The shell has had pipes for a while with nothing to pipe *through*, and an 18-row screen with no way to stop output scrolling off it. - grep [-vin] PAT [file]: substring match (no regex), stdin or a file, grep(1)'s exit convention. The other end of the pipe, at last. - more [file]: pages 17 lines at a time, --More-- bar in white-on-blue, any key pages, q quits. Counts *screen* rows, so wrapped lines pay their real cost. Keys come from pollin/pollcon, not stdin, so `cat big | more` still has a keyboard. - cp SRC DST: the fs has rm/mkdir but no way to duplicate a file. mv is this plus rm (there is no rename). Rejects SRC == DST, which would otherwise truncate the source via O_WRITE before reading a byte. - nc [-s] HOST PORT: raw TCP. Interactive (START sends the line with a CRLF, /q quits) or -s to pump stdin and print the reply with its own wall-clock idle timeout, because the kernel's blocking recv counts pumps, not seconds. Generalizes what chat/irc hardcode: `nc towel.blinkenlights.nl 23` works. sys_pollcon now pumps the link before draining the console ring. The ring is filled *by* net_pump, so a program that never touches the network polled a ring nothing would ever fill: host-injected keys (gbtype/gbdemo) hung `more` forever, and the Game Boy stopped answering pings for as long as it ran. KGetc already pumps for this reason. New tools take banks 35-38. Locals over statics in all four: a program's _DATA starts at $A000, where the shell leaves the command line.
Diffstat (limited to '')
-rw-r--r--usr/grep.c99
1 files changed, 99 insertions, 0 deletions
diff --git a/usr/grep.c b/usr/grep.c
new file mode 100644
index 0000000..3d2b325
--- /dev/null
+++ b/usr/grep.c
@@ -0,0 +1,99 @@
+#include "gbos.h"
+/* grep [-v][-i][-n] PAT [file]: print the lines of a file (or stdin) that
+ * contain the substring PAT. Plain substring matching, no regex - '.' and '*'
+ * are literal characters. -v inverts the match, -i folds case, -n prefixes
+ * each hit with its line number.
+ *
+ * NB: no statics here. A program's _DATA area starts at $A000, which is also
+ * where the shell leaves the command line (usr/sh.c ARGV0), so statics would
+ * eat argv. Locals live on the task stack below $BFFF and are always safe. */
+
+#define MAXLINE 80 /* two terminal rows; longer lines are clipped */
+
+static char fold(char c, unsigned char ic) {
+ if (ic && c >= 'A' && c <= 'Z') return (char)(c + 32);
+ return c;
+}
+
+/* is pat[] present anywhere in line[0..len)? */
+static unsigned char contains(char *line, unsigned char len, char *pat, unsigned char ic) {
+ unsigned char i, j;
+ if (!pat[0]) return 1; /* empty pattern matches all */
+ for (i = 0; i < len; i++) {
+ for (j = 0;; j++) {
+ if (!pat[j]) return 1; /* ran out of pattern: a hit */
+ if (i + j >= len) break; /* ran out of line: no hit here */
+ if (fold(line[i + j], ic) != fold(pat[j], ic)) break;
+ }
+ }
+ return 0;
+}
+
+void main(void) {
+ char *argv[8];
+ char line[MAXLINE];
+ unsigned char argc = argv_parse(argv, 8);
+ unsigned char inv = hasflag(argv, argc, 'v') ? 1 : 0;
+ unsigned char ic = hasflag(argv, argc, 'i') ? 1 : 0;
+ unsigned char num = hasflag(argv, argc, 'n') ? 1 : 0;
+ unsigned char i, len = 0, hits = 0, fd = NOFD;
+ unsigned int lineno = 0;
+ char *pat = 0, *fname = 0;
+ int ch;
+
+ /* first non-flag word is the pattern, second (if any) is the file */
+ for (i = 0; i < argc; i++) {
+ if (argv[i][0] == '-') continue;
+ if (!pat) pat = argv[i];
+ else {
+ fname = argv[i];
+ break;
+ }
+ }
+ if (!pat) {
+ puts("usage: grep [-vin] PAT [file]");
+ nl();
+ sexit(2);
+ }
+
+ if (fname) {
+ fd = open(fname, O_READ);
+ if (fd == EISDIR) {
+ puts("grep: is a directory");
+ nl();
+ sexit(2);
+ }
+ if (fd == NOFD) {
+ puts("grep: no such file");
+ nl();
+ sexit(2);
+ }
+ }
+
+ for (;;) {
+ if (fd == NOFD) {
+ char c = readc();
+ ch = (c == EOF) ? -1 : c;
+ } else ch = fgetc(fd);
+ if (ch == '\r') continue; /* CRLF: the LF ends the line */
+ if (ch >= 0 && ch != '\n') { /* ordinary character */
+ if (len < MAXLINE) line[len++] = (char)ch;
+ continue;
+ }
+ if (ch < 0 && len == 0) break; /* EOF, nothing pending */
+ lineno++;
+ if (contains(line, len, pat, ic) != inv) {
+ if (num) {
+ putu(lineno);
+ putc(':');
+ }
+ writes(line, len); /* one batched render */
+ nl();
+ hits = 1;
+ }
+ len = 0;
+ if (ch < 0) break; /* EOF after a final partial line */
+ }
+ if (fd != NOFD) close(fd);
+ sexit(hits ? 0 : 1); /* grep(1) exit convention */
+}