diff options
| author | user <user@clank> | 2026-07-16 07:30:34 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-16 07:30:34 +0200 |
| commit | 8682553498d32d3c18dea41c598ab40de18404a3 (patch) | |
| tree | 74096aa1fcefce24f80583ee10f25e5831a3b589 | |
| parent | libc + CLI tools: a small C userland (diff) | |
| download | gbos-8682553498d32d3c18dea41c598ab40de18404a3.tar.gz gbos-8682553498d32d3c18dea41c598ab40de18404a3.tar.xz gbos-8682553498d32d3c18dea41c598ab40de18404a3.zip | |
tools: wc, head, and an argc/argv demo (args) + libc.c helpers
- c/libc.c: shared C helpers linked into every program - putu (print decimal),
atou (parse decimal), argv_parse (tokenize getargs() into argc/argv).
- wc: count lines/words/chars of stdin. head [n]: first n lines (drains rest to
EOF). args: argc/argv demo.
- pid now uses libc putu; build.sh links libc.c; Makefile CBLOBS + libc dep.
- README: document the new tools + argv_parse.
- verified: 'args one two three' -> argc=3/argv[..]; wc '2 3 16'; head 2.
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | README.md | 39 | ||||
| -rw-r--r-- | c/args.c | 12 | ||||
| -rwxr-xr-x | c/build.sh | 3 | ||||
| -rw-r--r-- | c/gbos.h | 5 | ||||
| -rw-r--r-- | c/head.c | 16 | ||||
| -rw-r--r-- | c/libc.c | 34 | ||||
| -rw-r--r-- | c/pid.c | 7 | ||||
| -rw-r--r-- | c/wc.c | 20 | ||||
| -rw-r--r-- | include/gbos.inc | 3 | ||||
| -rw-r--r-- | src/programs.asm | 21 |
11 files changed, 144 insertions, 21 deletions
@@ -19,9 +19,12 @@ $(BUILD)/%.o: src/%.asm | $(BUILD) # C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \ - c/pid.bin c/cat.bin + c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin $(BUILD)/programs.o: $(CBLOBS) +# C programs also depend on the shared libc sources +$(CBLOBS): c/libc.c c/gbos.h + c/%.bin: c/%.c c/crt0.s c/libc.s c/build.sh sh c/build.sh $< $@ @@ -195,11 +195,19 @@ void main(void) { ### libc -`writes`, `putc`, `puts`, `nl`, `strlen`, `readc` (returns `EOF`=4 at end of -input), `getargs` (this program's argument string), `getpid`, `sexit`. +Syscall wrappers (`c/libc.s`): `writes`, `putc`, `puts`, `nl`, `strlen`, `readc` +(returns `EOF`=4 at end of input), `getargs` (this program's argument string), +`getpid`, `sexit`. C helpers (`c/libc.c`, linked into every program): `putu` +(print decimal), `atou` (parse decimal), `argv_parse` (tokenize into argc/argv). + Arguments: the shell splits the command line at the first space and leaves -`"cmd\0args\0"` at `$A000`; the child inherits it through `fork`, and -`getargs()` returns the args. +`"cmd\0args\0"` at `$A000`; the child inherits it through `fork`, `getargs()` +returns the raw arg string, and `argv_parse()` tokenizes it: + +```c +char *argv[8]; +unsigned char argc = argv_parse(argv, 8); /* args one two -> argc=3 */ +``` ### CLI tools (in `c/`) @@ -207,21 +215,27 @@ Arguments: the shell splits the command line at the first space and leaves |------|--------------| | `echo` | print its arguments (`echo hello world`) | | `cat` | copy stdin to stdout until EOF (Ctrl-D / pipe end) | +| `wc` | count lines / words / chars of stdin | +| `head` | print the first *n* lines of stdin (`head 5`, default 10) | +| `args` | argc/argv demo (`args one two three`) | | `uname`| print the system name | | `pid` | print the process's pid (decimal) | | `true` / `false` | exit 0 / 1 | | `chello` | the C "hello" demo | ``` -$ uname -gbos sm83 (Game Boy Color) $ echo C tools on a Game Boy C tools on a Game Boy -$ pid -5 -$ cat -hi from cat -hi from cat +$ args one two three +argc=3 +argv[0]=one +argv[1]=two +argv[2]=three +$ wc (input: "hello world\nfoo\n") +2 3 16 +$ head 2 (input: alpha/beta/gamma/delta) +alpha +beta ``` Each tool is a separate `c/<name>.c`, built to a ROM-bank blob, INCBIN'd, and @@ -244,7 +258,8 @@ explicit lengths and emit newlines via `nl()`. - [x] grow libc (`puts`/`putc`/`strlen`/`getargs`/EOF) + args via the shell - [x] CLI tools in C: `echo`, `cat`, `uname`, `pid`, `true`, `false` - [x] Makefile builds all C programs (a `CBLOBS` list) -- [ ] more tools (`wc`, `head`, `rev`), a real `argc/argv`, a RAM filesystem +- [x] `wc`, `head`, and an `argc/argv` demo (`args`) + `argv_parse` in libc +- [ ] more tools (`rev`, `grep`), option parsing (`-n`), a RAM filesystem - [ ] Preemptive scheduling: real context save in `TimerISR` → `hSwitchTo` - [ ] Use the `$D000-$DFFF` u-area (SVBK) for per-process kernel state / kstack - [ ] `brk`/heap allocator inside the task bank (heap up, stack down, collision = ENOMEM) diff --git a/c/args.c b/c/args.c new file mode 100644 index 0000000..c6f4db3 --- /dev/null +++ b/c/args.c @@ -0,0 +1,12 @@ +#include "gbos.h" +/* args: an argc/argv demo. Splits its argument string into argv[] and prints + argc plus each argument. Try: `args one two three` */ +void main(void) { + char *argv[8]; + unsigned char argc = argv_parse(argv, 8); + unsigned char i; + puts("argc="); putu(argc); nl(); + for (i = 0; i < argc; i++) { + puts("argv["); putu(i); puts("]="); puts(argv[i]); nl(); + } +} @@ -8,12 +8,13 @@ LIBDIR=/usr/share/sdcc/lib/sm83 mkdir -p "$B" sdasgb -o "$B/crt0.rel" c/crt0.s sdasgb -o "$B/libc.rel" c/libc.s +sdcc -msm83 -c -Ic c/libc.c -o "$B/libc_c.rel" sdcc -msm83 -c -Ic "$SRC" -o "$B/prog.rel" # link: crt0 first (=> _start at 0x4000), then libc, program, and sm83 lib sdldgb -n -m -w -j -i "$B/prog.ihx" \ -b _CODE=0x4000 -b _DATA=0xa000 \ -k "$LIBDIR" -l sm83 \ - "$B/crt0.rel" "$B/libc.rel" "$B/prog.rel" + "$B/crt0.rel" "$B/libc.rel" "$B/libc_c.rel" "$B/prog.rel" makebin -Z -p "$B/prog.ihx" "$B/prog.bin" dd if="$B/prog.bin" of="$OUT" bs=1024 skip=16 count=16 2>/dev/null echo "wrote $OUT ($(wc -c < "$OUT") bytes); entry:" @@ -12,4 +12,9 @@ char *getargs(void); /* this program's argument string */ char readc(void); /* one input byte, EOF at end */ void sexit(unsigned char code); unsigned char getpid(void); + +/* helpers (c/libc.c) */ +void putu(unsigned int n); /* print decimal */ +unsigned char atou(const char *s); /* parse decimal */ +unsigned char argv_parse(char **argv, unsigned char maxv); /* argc/argv */ #endif diff --git a/c/head.c b/c/head.c new file mode 100644 index 0000000..9363b1c --- /dev/null +++ b/c/head.c @@ -0,0 +1,16 @@ +#include "gbos.h" +/* head [n]: print the first n lines of stdin (default 10). Drains the rest to + EOF so the shell sees a clean end of input. */ +void main(void) { + char *a = getargs(); + unsigned char n = 10; + unsigned char lines = 0; + char c; + if (*a) n = atou(a); + for (;;) { + c = readc(); + if (c == EOF) break; + if (lines < n) putc(c); + if (c == '\n') lines++; + } +} diff --git a/c/libc.c b/c/libc.c new file mode 100644 index 0000000..3b9883b --- /dev/null +++ b/c/libc.c @@ -0,0 +1,34 @@ +/* gbos libc C helpers (compiled once, linked into every program). */ +#include "gbos.h" + +/* print an unsigned int in decimal */ +void putu(unsigned int n) { + char buf[5]; + unsigned char i = 0; + if (n == 0) { putc('0'); return; } + while (n) { buf[i++] = '0' + (n % 10); n = n / 10; } + while (i) putc(buf[--i]); +} + +/* parse a decimal number (stops at the first non-digit) */ +unsigned char atou(const char *s) { + unsigned char n = 0; + while (*s >= '0' && *s <= '9') { n = n * 10 + (*s - '0'); s++; } + return n; +} + +/* tokenize the argument string in place into argv[]; returns argc. + (replaces the spaces in the arg string with NULs) */ +unsigned char argv_parse(char **argv, unsigned char maxv) { + char *p = getargs(); + unsigned char argc = 0; + for (;;) { + while (*p == ' ') p++; + if (*p == 0) break; + if (argc < maxv) argv[argc] = p; + argc++; + while (*p != 0 && *p != ' ') p++; + if (*p == ' ') *p++ = 0; + } + return argc; +} @@ -1,9 +1,2 @@ #include "gbos.h" -/* print an unsigned byte as decimal (no runtime division: subtract-based) */ -static void putu(unsigned char n) { - char buf[3]; unsigned char i = 0; - if (n == 0) { putc('0'); return; } - while (n) { buf[i++] = '0' + (n % 10); n = n / 10; } - while (i) putc(buf[--i]); -} void main(void) { putu(getpid()); nl(); } @@ -0,0 +1,20 @@ +#include "gbos.h" +/* wc: count lines, words, chars of stdin (like `wc`). */ +void main(void) { + unsigned int lines = 0, words = 0, chars = 0; + unsigned char inword = 0; + char c; + for (;;) { + c = readc(); + if (c == EOF) break; + chars++; + if (c == '\n') lines++; + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { + inword = 0; + } else if (!inword) { + inword = 1; + words++; + } + } + putu(lines); putc(' '); putu(words); putc(' '); putu(chars); nl(); +} diff --git a/include/gbos.inc b/include/gbos.inc index 01d7f1a..156876b 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -99,5 +99,8 @@ DEF PROG_FALSE EQU 6 DEF PROG_UNAME EQU 7 DEF PROG_PID EQU 8 DEF PROG_CAT EQU 9 +DEF PROG_WC EQU 10 +DEF PROG_HEAD EQU 11 +DEF PROG_ARGS EQU 12 ENDC diff --git a/src/programs.asm b/src/programs.asm index 322c7fc..a3a6db2 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -70,6 +70,15 @@ ProgPid: SECTION "prog_cat", ROMX[$4000], BANK[11] ProgCat: INCBIN "c/cat.bin" +SECTION "prog_wc", ROMX[$4000], BANK[12] +ProgWc: + INCBIN "c/wc.bin" +SECTION "prog_head", ROMX[$4000], BANK[13] +ProgHead: + INCBIN "c/head.bin" +SECTION "prog_args", ROMX[$4000], BANK[14] +ProgArgs: + INCBIN "c/args.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - a tiny shell. @@ -202,6 +211,12 @@ ProgSh: db PROG_PID db "cat", 0 db PROG_CAT + db "wc", 0 + db PROG_WC + db "head", 0 + db PROG_HEAD + db "args", 0 + db PROG_ARGS db 0 ; terminator ; ----------------------------------------------------------------------------- @@ -256,3 +271,9 @@ ProgramTable:: dw ProgPid db LOW(BANK(ProgCat)), HIGH(BANK(ProgCat)) dw ProgCat + db LOW(BANK(ProgWc)), HIGH(BANK(ProgWc)) + dw ProgWc + db LOW(BANK(ProgHead)), HIGH(BANK(ProgHead)) + dw ProgHead + db LOW(BANK(ProgArgs)), HIGH(BANK(ProgArgs)) + dw ProgArgs |
