do not edit — generated by btf.
git.druid.rocksindexdruid520kaboomsrc/user/ed.nsc

src/user/ed.nsc


include "syscalls.nsh";
include "lib.nsh";
include "string.nsh";
include "mem.nsh";
include "stdio.nsh";
 
/*
 * ed: tape-kernel's own line editor (src/usr/editor.c there), ported
 * -- not reinvented -- to kaboom/nsc. the real logic is unchanged:
 * a fixed 32-line x 80-column buffer, load a file by splitting on
 * '\n', a command loop where 'e' re-enters every line fresh (empty
 * line stops early) and 'w' joins the buffer back with '\n' and
 * saves it, 'q' quits. same MAX_LINES/MAX_LINE_LEN as the original
 * (32/80). two small, real additions past the original: 'a' appends
 * lines after whatever's already there instead of always starting
 * over, and the display now shows each line's number -- both asked
 * for directly, not part of the original port.
 *
 * one honest adaptation, not a logic change: tape-kernel's version
 * repaints the whole screen at fixed (row, col) positions every
 * loop iteration (editor_display's prt/cob calls) and reads single
 * keys with a raw, no-echo gtchr(). kaboom's vga driver only exposes
 * sequential, scrolling text output plus a hardware clear (vga_clear,
 * syscall 16) -- there is no absolute-position write or a real
 * hardware cursor api yet, so this prints the same information
 * (filename, hotkeys, current lines) sequentially after a clear
 * instead of redrawing it in place. sys_read(fd, buf, 1) already
 * blocks for exactly one keystroke (kaboom's keyboard driver echoes
 * at the driver level, same as every other read here), which is
 * exactly what gtchr() gave the original.
 *
 * kaboom has no arrays-of-arrays (array elements must be a scalar
 * type), so the 32x80 line buffer tape-kernel declared as
 * char[MAX_LINES][MAX_LINE_LEN+1] is one flat i8[MAX_LINES*81] here,
 * indexed as lines[line*81 + col] -- same data, same layout, just
 * addressed by hand the way every other flat table in this kernel
 * already is (kfs_dir_list's names_out, for one).
 *
 * every working buffer of any real size (the line table, the load
 * buffer, the save buffer) is sys_alloc'd now, not a stack local or
 * a fixed global array: kaboom built a real arena allocator (see
 * alloc.nsc) specifically so nothing would need to hand-declare
 * fixed-size buffers for "give me some memory, once, at runtime" --
 * an earlier version of this file did exactly that anyway, before
 * the allocator was actually wired up to userspace.
 */
 
/* loads filename into lines, returning 1 if ed can go on to edit it
 * or 0 if it must refuse (the caller exits without ever reaching a
 * 'w'). only a file that genuinely doesn't exist starts a blank
 * buffer. anything else that can't be loaded COMPLETELY is refused
 * with a message: a permission problem, something that isn't a plain
 * file, or content that doesn't fit ed's fixed 32 lines x 79 columns
 * (or its 4096-byte load buffer, or contains a nul byte, which the
 * nul-terminated line table can't hold). this used to treat every
 * open failure as a new file, and silently cut an oversized file down
 * to what fit -- after which 'w' overwrote the real, full file with
 * that fragment (`cp /bin/ls lscopy; ed lscopy`, 'w', left 3 bytes
 * of a 16952-byte binary). */
i32
editor_load(ptr filename, u64 filename_len, ptr lines, ptr line_count_out)
{
	ptr content;
	i64 clen;
	i64 size;
	i64 type;
	i32 fd;
	u64 ci;
	i32 line;
	u64 pos;
	u8 ch;
 
	line = 0;
	pos = 0;
	*line_count_out = (i64)0;
 
	type = sys_stat(filename, filename_len);
	if(type < (i64)0)
	{
		if(sys_errno() == (i64)1)
		{
			print_err(2, "ed", "cannot open");
			return 0; /* a directory on the way can't be traversed */
		}
		return 1; /* new file: genuinely nothing yet */
	}
	if(type != (i64)1)
	{
		fputs(2, "ed: not a plain file\n");
		return 0;
	}
 
	fd = sys_open(filename, filename_len);
	if(fd < 0)
	{
		print_err(2, "ed", "cannot open");
		return 0;
	}
	size = sys_fsize(fd);
	if(size < (i64)0 || size > (i64)4096)
	{
		sys_close(fd);
		fputs(2, "ed: file too big for ed, not opened\n");
		return 0;
	}
 
	/* sys_alloc'd, not a stack array: see the file header note on why
	 * ed's own working buffers moved off the stack/off fixed globals. */
	content = sys_alloc((u64)4096);
	if(content == (ptr)0)
	{
		sys_close(fd);
		fputs(2, "ed: out of memory\n");
		return 0;
	}
	memset(content, (u8)0, (u64)4096);
 
	clen = 0;
	if(size > (i64)0)
	{
		clen = sys_read(fd, content, (u64)size);
	}
	sys_close(fd);
	if(clen != size)
	{
		fputs(2, "ed: read failed\n");
		return 0;
	}
 
	ci = 0;
	while(ci < (u64)clen)
	{
		ch = ptr_byte_at(content, ci);
		if(ch == (u8)0)
		{
			fputs(2, "ed: file contains a nul byte, not opened\n");
			return 0;
		}
		if(line >= 32)
		{
			fputs(2, "ed: file has more than 32 lines, not opened\n");
			return 0;
		}
		if(ch == (u8)10)
		{
			mem_byte_set(lines, (u64)line * (u64)81 + pos, (u8)0);
			line = line + 1;
			pos = 0;
		}
		else if(pos < (u64)79)
		{
			mem_byte_set(lines, (u64)line * (u64)81 + pos, ch);
			pos = pos + (u64)1;
		}
		else
		{
			fputs(2, "ed: a line is longer than 79 columns, not opened\n");
			return 0;
		}
		ci = ci + (u64)1;
	}
	if(pos > (u64)0)
	{
		mem_byte_set(lines, (u64)line * (u64)81 + pos, (u8)0);
		line = line + 1;
	}
	*line_count_out = (i64)line;
	return 1;
}
 
void
editor_save(ptr filename, u64 filename_len, ptr lines, i32 line_count)
{
	ptr buf;
	u64 pos;
	i32 i;
	u64 j;
	u8 ch;
 
	buf = sys_alloc((u64)2688);
	if(buf == (ptr)0)
	{
		fputs(2, "ed: out of memory, not saved\n");
		return;
	}
	pos = 0;
	i = 0;
	while(i < line_count)
	{
		j = 0;
		ch = ptr_byte_at(lines, (u64)i * (u64)81 + j);
		while(ch != (u8)0 && pos < (u64)2686)
		{
			mem_byte_set(buf, pos, ch);
			pos = pos + (u64)1;
			j = j + (u64)1;
			ch = ptr_byte_at(lines, (u64)i * (u64)81 + j);
		}
		/* every line gets a trailing \n, including the last -- real
		 * text files end in one (editor_load can't actually tell
		 * whether the file it read already had a final newline or
		 * not: splitting "a\nb\n" and "a\nb" on '\n' produces the
		 * identical in-memory ["a","b"], so always writing one back
		 * out is the only consistent choice, and matches what every
		 * other real editor/tool assumes a text file looks like). */
		if(pos < (u64)2686)
		{
			mem_byte_set(buf, pos, (u8)10);
			pos = pos + (u64)1;
		}
		i = i + 1;
	}
 
	if(pos > (u64)0)
	{
		if(sys_writefile(filename, filename_len, buf, pos) == 1)
		{
			fputs(1, "saved!\n");
		}
		else
		{
			print_err(1, "ed", "save failed");
		}
	}
	else
	{
		fputs(1, "empty, not saved\n");
	}
}
 
/* displays each occupied line prefixed with its 1-based number
 * ("1: text", ...) -- a small, real, explicitly requested addition
 * past tape-kernel's own original, which showed lines bare. */
void
editor_display(ptr filename, u64 filename_len, ptr lines, i32 line_count)
{
	i32 i;
	ptr lineptr;
 
	sys_vga_clear();
	sys_write(1, filename, filename_len); /* argv string, not a literal -- stays sys_write */
	puts("\n[e] edit  [a] append  [w] save  [q] quit\n");
 
	i = 0;
	while(i < line_count)
	{
		lineptr = lines + (u64)i * (u64)81;
		if(ptr_byte_at(lineptr, (u64)0) != (u8)0)
		{
			put_udec((u64)(i + 1));
			puts(": ");
			puts(lineptr);
			putchar((u8)10);
		}
		i = i + 1;
	}
	stdio_flush();
}
 
/* reads lines from stdin starting at index start (0 for a fresh 'e',
 * the current line_count for an 'a' append), stopping at an empty
 * line or MAX_LINES (32), writing through mem_byte_set since lines is
 * a bare ptr here, not the real array main() itself declares it as.
 * returns the new line count.
 *
 * done is a separate flag from i deliberately: an earlier version
 * used i==32 itself as the "stop" signal (breaking the while(i<32)
 * condition), then returned i as the new count -- so stopping via an
 * empty line (the normal case, not filling all 32 slots) always
 * reported a line count of 32 instead of how many lines were
 * actually entered. editor_save's own loop trusts line_count to know
 * where the real content ends, so every save after a normal e/a
 * session was appending one extra newline per phantom "line" 32
 * minus the real count -- found by actually cat'ing a saved file and
 * seeing ~29 trailing blank lines, not by reading the code. */
i32
editor_enter_lines(ptr lines, i32 start)
{
	i8 linebuf[88];
	i64 linelen;
	i32 i;
	i32 done;
	u64 k;
 
	i = start;
	done = 0;
	while(i < 32 && done == 0)
	{
		fputs(1, "> "); /* unbuffered -- must appear before the blocking read below */
		linelen = sys_read(0, &linebuf[0], (u64)82);
		if(linelen <= (i64)0)
		{
			done = 1;
		}
		else
		{
			if(ptr_byte_at(&linebuf[0], (u64)(linelen - (i64)1)) == (u8)10)
			{
				linebuf[linelen - (i64)1] = 0;
				linelen = linelen - (i64)1;
			}
			if(linelen == (i64)0)
			{
				done = 1; /* empty line: stop */
			}
			else
			{
				if(linelen > (i64)79)
				{
					linelen = (i64)79;
				}
				k = 0;
				while((i64)k < linelen)
				{
					mem_byte_set(lines, (u64)i * (u64)81 + k, ptr_byte_at(&linebuf[0], k));
					k = k + (u64)1;
				}
				mem_byte_set(lines, (u64)i * (u64)81 + k, (u8)0);
				i = i + 1;
			}
		}
	}
	return i;
}
 
global i32
main(i32 argc, ptr argv)
{
	ptr lines;
	i64 line_count;
	ptr filename;
	u64 filename_len;
	i8 key[8];
 
	if(argc < 2)
	{
		fputs(2, "usage: ed file\n");
		return 1;
	}
	filename = argv_get(argv, 1);
	filename_len = strlen(filename);
 
	/* sys_alloc'd, not a stack array -- see the file header note. */
	lines = sys_alloc((u64)2592);
	if(lines == (ptr)0)
	{
		fputs(2, "ed: out of memory\n");
		return 1;
	}
	memset(lines, (u8)0, (u64)2592);
 
	if(editor_load(filename, filename_len, lines, &line_count) == 0)
	{
		return 1; /* refused -- see editor_load; nothing was touched */
	}
 
	while(1)
	{
		editor_display(filename, filename_len, lines, (i32)line_count);
 
		key[0] = 0;
		sys_read(0, &key[0], (u64)1);
 
		if(ptr_byte_at(&key[0], (u64)0) == (u8)101) /* 'e' */
		{
			sys_vga_clear();
			puts("enter each line, empty line to stop\n");
			line_count = (i64)editor_enter_lines(lines, 0);
			if(line_count == (i64)0)
			{
				mem_byte_set(lines, (u64)0, (u8)0);
				line_count = 1;
			}
		}
		else if(ptr_byte_at(&key[0], (u64)0) == (u8)97) /* 'a' */
		{
			sys_vga_clear();
			puts("append lines, empty line to stop\n");
			line_count = (i64)editor_enter_lines(lines, (i32)line_count);
		}
		else if(ptr_byte_at(&key[0], (u64)0) == (u8)119) /* 'w' */
		{
			editor_save(filename, filename_len, lines, (i32)line_count);
		}
		else if(ptr_byte_at(&key[0], (u64)0) == (u8)113) /* 'q' */
		{
			sys_vga_clear();
			return 0;
		}
	}
	return 0;
}
powered by btf.