sys

A set of unix utils in hare!
Log | Files | Refs | README

commit b3dc25152c5bcb8a7b5009c689b077a1e6f950c3
parent 93c23879dc64d8291320b28235d3a5e45b773173
Author: thing1 <thing1@seacrossedlovers.xyz>
Date:   Thu, 26 Feb 2026 18:37:39 +0000

added ed!

Diffstat:
A:wq | 26++++++++++++++++++++++++++
MMakefile | 13++++++++++++-
MTODO.md | 1+
Acmd/ed/cmds.ha | 26++++++++++++++++++++++++++
Acmd/ed/file.ha | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/ed/main.ha | 44++++++++++++++++++++++++++++++++++++++++++++
Acmd/mc.ha | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcmd/uniq.ha | 2+-
8 files changed, 343 insertions(+), 2 deletions(-)

diff --git a/:wq b/:wq @@ -0,0 +1,26 @@ +export type option = enum { + CANT, + WILL, + MUST, +}; + +export type cmdfn = fn(file: *lines, current: size, args: []str) void; + +export type cmds = struct { + // the char to call the command + cmd: rune, + // help text for the command + msg: str, + + // does the command allow a range + range: option, + + // does the command allow a repeater + repeat: option, + + // the number of arguments a cmd can take + args: size, + + // function + f: *cmdfn, +}; diff --git a/Makefile b/Makefile @@ -7,13 +7,21 @@ DESTDIR= PREFIX=/usr/local BINDIR=$(PREFIX)/bin -all: bin/ls bin/rainbow bin/cat bin/uniq bin/split bin/wc bin/yes +all: bin/ls bin/rainbow bin/cat bin/uniq bin/split bin/wc bin/yes bin/mc bin/ed clean: rm -rf bin/* +check: + hare test cmd/*.ha + hare test cmd/ed/ + .PHONY: all check clean install uninstall +EDSRC != find cmd/ed/ -name *.ha +bin/ed: $(EDSRC) + $(HARE) build $(HAREFLAGS) -o $@ cmd/ed/ + bin/ls: cmd/ls.ha $(HARE) build $(HAREFLAGS) -o $@ cmd/ls.ha @@ -35,3 +43,6 @@ bin/wc: cmd/wc.ha bin/yes: cmd/yes.ha $(HARE) build $(HAREFLAGS) -o $@ cmd/yes.ha +bin/mc: cmd/mc.ha + $(HARE) build $(HAREFLAGS) -o $@ cmd/mc.ha + diff --git a/TODO.md b/TODO.md @@ -1,3 +1,4 @@ +- mc (finish) - fmt - build system - shell diff --git a/cmd/ed/cmds.ha b/cmd/ed/cmds.ha @@ -0,0 +1,26 @@ +export type option = enum { + CANT, + WILL, + MUST, +}; + +export type cmdfn = fn(file: *lines, current: size, args: []str) void; + +export type cmds = struct { + // the char to call the command + cmd: rune, + // help text for the command + msg: str, + + // does the command allow a range + range: option, + + // does the command allow a repeater + repeat: option, + + // the number of arguments a cmd can take + args: size, + + // function + f: *cmdfn, +}; diff --git a/cmd/ed/file.ha b/cmd/ed/file.ha @@ -0,0 +1,171 @@ +use fmt; + +use util; + +export type lines = struct { + next: nullable *lines, + prev: nullable *lines, + s: str +}; + +// returns a new allocated line, the caller should free this value +export fn newline(s: str) *lines = alloc(lines{next = null, prev = null, s = s})!; +@test fn newline() void = assert(newline("hello world").s == "hello world"); + +// returns the last line of [[lines]] +export fn getend(ls: *lines) *lines = { + let current = ls; + for (let i = 0; true; i += 1) + current = match (current.next) { + case let ls: *lines => yield ls; + case null => return current; + }; +}; +@test fn getend() void = { + let line = newline("hello world"); + assert(getend(line) == line); +}; +@test fn getend() void = { + let line = newline("hello world"); + line.next = newline("bye world"); + assert(getend(line) == line.next); +}; + +// inserts [[l]] at [[where]] into [[ls]], and returns a new value for [[ls]], +// the caller should override [[ls]], with the return value +export fn insertline(ls: *lines, l: *lines, where: size) *lines = { + if (where == 0) { + l.next = ls; //todo, make this work for multiple lines in l + ls.prev = l; + return l; + }; + + let current = getline(ls, where - 1); + + let tmp = match (current.next) { + case let ls: *lines => yield ls; + case null => + current.next = l; + l.prev = current; + return ls; + }; + let end = getend(l); + + current.next = l; + l.prev = current; + end.next = tmp; + tmp.prev = end; + + return ls; +}; +@test fn insertline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 1); + assert((ls.next as *lines).s == "hello world"); + assert(ls.s == "file"); +}; +@test fn insertline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 0); + assert(ls.s == "hello world"); + assert((ls.next as *lines).s == "file"); +}; + +// gets the [[where]]'th line from [[ls]] +export fn getline(ls: *lines, where: size) *lines = { + let current = ls; + for (let i = 0z; i < where; i += 1) + current = match (current.next) { + case let ls: *lines => yield ls; + case null => abort(); + }; + + return current; +}; +@test fn getline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 1); + assert(getline(ls, 0).s == "file"); +}; +@test fn getline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 1); + assert(getline(ls, 1).s == "hello world"); +}; +@test fn getline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 0); + assert(getline(ls, 0).s == "hello world"); +}; +@test fn getline() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 0); + assert(getline(ls, 1).s == "file"); +}; + +// counts how many lines are in [[ls]] +export fn countlines(ls: *lines) size = { + let current = ls; + for (let i = 0z; true; i += 1) + current = match (current.next) { + case let ls: *lines => yield ls; + case null => return i + 1; + }; +}; +@test fn countlines() void = { + let ls = newline("file"); + let line = newline("hello world"); + ls = insertline(ls, line, 0); + assert(countlines(ls) == 2); +}; + +// deletes line at [[where]] from [[ls]], the caller should override [[ls]] with +// the return value +export fn deleteline(ls: *lines, where: size) *lines = { + if (where == 0) return ls.next as *lines; + + let current = getline(ls, where); + + (current.prev as *lines).next = current.next; + if (where != countlines(ls)) + (current.next as *lines).prev = current.prev; + + free(current); + return ls; +}; +@test fn deleteline() void = { + let ls = newline("file"); + let line = newline("hello world"); + let line2 = newline("fuck"); + ls = insertline(ls, line, 1); + ls = insertline(ls, line2, 2); + ls = deleteline(ls, 0); + assert(ls.s == "hello world"); + assert((ls.next as *lines).s == "fuck"); +}; +@test fn deleteline() void = { + let ls = newline("file"); + let line = newline("hello world"); + let line2 = newline("fuck"); + ls = insertline(ls, line, 1); + ls = insertline(ls, line2, 2); + ls = deleteline(ls, 1); + assert(ls.s == "file"); + assert((ls.next as *lines).s == "fuck"); +}; +@test fn deleteline() void = { + let ls = newline("file"); + let line = newline("hello world"); + let line2 = newline("fuck"); + ls = insertline(ls, line, 1); + ls = insertline(ls, line2, 2); + ls = deleteline(ls, 2); + assert(ls.s == "file"); + assert((ls.next as *lines).s == "hello world"); +}; diff --git a/cmd/ed/main.ha b/cmd/ed/main.ha @@ -0,0 +1,44 @@ +use fmt; +use io; +use bufio; +use strings; +use fs; +use os; + +use util; + +fn loadfile(h: io::handle) nullable *lines = { + let ls: nullable *lines = null; + let sc = bufio::newscanner(h); + defer bufio::finish(&sc); + + for (true) { + match (bufio::scan_line(&sc)) { + case let s: str => + match (ls) { + case let ls: *lines => + ls.next = newline(strings::dup(s)!); + (ls.next as *lines).prev = ls; + case null => ls = newline(strings::dup(s)!); + }; + case io::EOF => break; + case => abort(); + }; + }; + + return ls; +}; + +fn openfile(name: str) nullable *lines = { + let h = match(os::open(name)) { + case let h: io::file => yield h; + case let e: fs::error => util::die(fs::strerror(e)); + }; + defer io::close(h)!; + + return loadfile(h); +}; + +export fn main() void = { + abort(); +}; diff --git a/cmd/mc.ha b/cmd/mc.ha @@ -0,0 +1,62 @@ +use fmt; +use os; +use io; +use bufio; +use unix::tty; +use strings; +use strconv; +use getopt; + +use util; + +let target = 8z; + +export fn main() void = { + const cmd = getopt::parse(os::args, + "Multi column output", + ('c', "count", "Target count (default 8)") + ); + for (let opt .. cmd.opts) { + switch (opt.0) { + case 'c' => + target = match (strconv::stou(opt.1)) { + case let i: uint => yield i: size; + case strconv::invalid => util::die("value must be an integer"); + case strconv::overflow => util::die("value too large"); + }; + case => abort(); + }; + }; + + let tty = tty::open()!; + let sz = tty::winsize(tty)!; + let sc = bufio::newscanner(os::stdin); + defer io::close(&sc)!; + + let lines: []str = []; + defer free(lines); + + let longest = 0z; + + for (true) { + match (bufio::scan_line(&sc)) { + case let s: str => + append(lines, strings::dup(s)!)!; + if (len(s) > longest) longest = len(s); + case io::EOF => break; + case => abort(); // TODO change + }; + }; + + let colcount = sz.columns / (longest + 3); + if (target < colcount) colcount = target; + let colwidth = longest + 3; + + for (let i = 0z; i < len(lines); i += 1) { + if (i != 0 && i % colcount == 0) fmt::println()!; + fmt::print( + strings::rpad(lines[i], ' ', colwidth)! + )!; + }; + fmt::println()!; +}; diff --git a/cmd/uniq.ha b/cmd/uniq.ha @@ -22,7 +22,7 @@ fn run() void = { for (true) { match (bufio::scan_line(&sc)) { - case let s: str => append(lines, s)!; + case let s: str => append(lines, strings::dup(s)!)!; case io::EOF => break; case let e: utf8::invalid => util::die(utf8::strerror(e)); case let e: io::error => util::die(io::strerror(e));