commit 1ed775fdc4cf5c7abaecb5cac5fa8fe36dae1b38
parent e741b8bd1d5c4129c3ece8a86789d551479a6f20
Author: Byron Torres <b@torresjrjr.com>
Date: Fri, 2 Dec 2022 16:22:41 +0000
progress
Diffstat:
M | Makefile | | | 2 | ++ |
A | address.ha | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
M | buffer.ha | | | 38 | ++++++++++++++++++++++++++------------ |
M | command.ha | | | 292 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
M | main.ha | | | 68 | +++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
A | parse.ha | | | 352 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 763 insertions(+), 38 deletions(-)
diff --git a/Makefile b/Makefile
@@ -3,8 +3,10 @@ HAREFLAGS=
source=\
main.ha \
+ address.ha \
buffer.ha \
command.ha \
+ parse.ha \
util.ha \
all: ed
diff --git a/address.ha b/address.ha
@@ -0,0 +1,49 @@
+use regex;
+
+type address = struct {
+ addrtype: addresstype,
+ lineoffset: int,
+};
+
+type addresstype = (size | currentline | lastline | rune);
+
+type currentline = void;
+
+type lastline = void;
+
+type badaddress = !void;
+
+fn addr_nextline(s: *session, n: size) size = {
+ if (len(s.buf.lines) - 1 == n) {
+ return n;
+ };
+ return n + 1;
+};
+
+fn addr_prevline(s: *session, n: size) size = {
+ if (n == 0 || n == 1) {
+ return n;
+ };
+ return n - 1;
+};
+
+fn addr_linenum(s: *session, n: size) (size | badaddress) = {
+ if (n > len(s.buf.lines)) {
+ return badaddress;
+ };
+ return n;
+};
+
+fn addr_lastline(s: *session) size = {
+ return len(s.buf.lines) - 1z;
+};
+
+fn addr_mark(s: *session, mark: rune) (size | badaddress) = {
+ for (let n = 1z; n < len(s.buf.lines); n += 1) {
+ const l = s.buf.lines[n];
+ if (l.mark == mark) {
+ return n;
+ };
+ };
+ return badaddress;
+};
diff --git a/buffer.ha b/buffer.ha
@@ -4,10 +4,11 @@ use io;
use strings;
type buffer = struct {
- filename: (void | str),
+ filename: str,
lines: []*line,
trash: []*line,
cursor: size,
+ modified: bool,
};
type line = struct {
@@ -19,16 +20,25 @@ type line = struct {
fn buf_deleteall(buf: *buffer) void = {
buf_wipetrash(buf);
- for (len(buf.lines) > 1) {
- insert(buf.trash[0], buf.lines[1]);
- delete(buf.lines[1]);
+ if (len(buf.lines) > 1) {
+ insert(buf.trash[0], buf.lines[1..]...);
+ delete(buf.lines[1..]);
};
};
-fn buf_read(buf: *buffer, src: io::handle, a: size) size = {
+fn buf_delete(buf: *buffer, a: size, b: size) void = {
+ fmt::errorfln("DEBUG: buf_delete(a={}, b={})", a, b)!;
buf_wipetrash(buf);
- let newlines: []*line = [];
+ insert(buf.trash[0], buf.lines[a..b+1]...);
+ delete(buf.lines[a..b+1]);
+};
+
+fn buf_read(buf: *buffer, src: io::handle, a: size) (size, size) = {
+ buf_wipetrash(buf);
+
+ let ls: []*line = [];
+ defer free(ls);
let sz = 0z;
for (true) {
const bytes = match (bufio::scanline(src)) {
@@ -40,25 +50,29 @@ fn buf_read(buf: *buffer, src: io::handle, a: size) size = {
abort();
};
- sz += len(bytes);
+ sz += len(bytes) + 1; // TODO: handle newlines better
const text = strings::fromutf8(bytes)!;
- append(newlines, alloc(line { text = text, ... }));
+ append(ls, alloc(line { text = text, ... }));
};
- insert(buf.lines[a + 1], newlines...);
- return sz;
+ insert(buf.lines[a + 1], ls...);
+ const lenls = len(ls);
+ return (sz, lenls);
};
-fn buf_write(buf: *buffer, dest: io::handle, a: size, b: size) void = {
+fn buf_write(buf: *buffer, dest: io::handle, a: size, b: size) size = {
+ let sz = 0z;
for (let n = a; n <= b; n += 1) {
- fmt::fprintln(dest, buf.lines[n].text)!;
+ sz += fmt::fprintln(dest, buf.lines[n].text)!;
};
+ return sz;
};
fn buf_wipetrash(buf: *buffer) void = {
for (len(buf.trash) > 0) {
free(buf.trash[0].text);
+ free(buf.trash[0]);
delete(buf.trash[0]);
};
};
diff --git a/command.ha b/command.ha
@@ -1,19 +1,281 @@
+use errors;
use fmt;
use fs;
use io;
use os;
-fn cmd_edit(s: *session, file: (void | str)) void = {
- if (file is void) {
- if (s.buffer.filename is void) {
- return errormsg(s, "No current filename");
+// Executes the session's .cmd command.
+fn execute(s: *session) void = {
+ // first, execute the addresses
+ let cursor = s.buf.cursor;
+ let lns: []size = [];
+ // defer free(lns);
+ for (let i = 0z; i < len(s.cmd.addrs); i += 1) {
+ const addr = s.cmd.addrs[i];
+ let n = match (addr.addrtype) {
+ case let n: size =>
+ yield addr_linenum(s, n)!;
+ case currentline =>
+ yield cursor;
+ case lastline =>
+ yield addr_lastline(s);
+ case let m: rune =>
+ yield addr_mark(s, m)!;
};
+ n = (n: int + addr.lineoffset): size;
+ append(lns, n);
+ };
+
+ // then, execute the full command
+ switch (s.cmd.cmdtype) {
+ case commandtype::APPEND =>
+ fmt::errorfln("TODO: append")!;
+ case commandtype::CHANGE =>
+ fmt::errorfln("TODO: change")!;
+ case commandtype::DELETE =>
+ const (a, b) = get_range(lns, cursor, cursor);
+ cmd_delete(s, s.cmd.arg, a, b);
+ case commandtype::EDIT =>
+ cmd_edit(s, s.cmd.arg);
+ case commandtype::EDIT_FORCED =>
+ fmt::errorfln("TODO: edit_forced")!;
+ case commandtype::FILENAME =>
+ if (!assert_noaddrs(s, lns)) return;
+ cmd_filename(s, s.cmd.arg);
+ case commandtype::GLOBAL =>
+ fmt::errorfln("TODO: global")!;
+ case commandtype::GLOBAL_INTERACTIVE =>
+ fmt::errorfln("TODO: global_interactive")!;
+ case commandtype::HELP =>
+ if (!assert_noaddrs(s, lns)) return;
+ cmd_help(s);
+ case commandtype::HELPMODE =>
+ if (!assert_noaddrs(s, lns)) return;
+ cmd_helpmode(s);
+ case commandtype::INSERT =>
+ fmt::errorfln("TODO: insert")!;
+ case commandtype::JOIN =>
+ fmt::errorfln("TODO: join")!;
+ case commandtype::MARK =>
+ fmt::errorfln("TODO: mark")!;
+ case commandtype::LIST =>
+ fmt::errorfln("TODO: list")!;
+ case commandtype::MOVE =>
+ fmt::errorfln("TODO: move")!;
+ case commandtype::NUMBER =>
+ const (a, b) = get_range(lns, cursor, cursor);
+ if (a > b) {
+ //return badaddress;
+ errormsg(s, "Invalid address");
+ return;
+ };
+ cmd_number(s, a, b);
+ case commandtype::PRINT =>
+ const (a, b) = get_range(lns, cursor, cursor);
+ cmd_print(s, a, b);
+ case commandtype::PROMPT=>
+ if (!assert_noaddrs(s, lns)) return;
+ cmd_prompt(s);
+ case commandtype::QUIT =>
+ fmt::errorfln("TODO: quit")!;
+ case commandtype::READ =>
+ const n = get_linenum(lns, cursor);
+ cmd_read(s, s.cmd.arg, n);
+ case commandtype::SUBSTITUTE =>
+ fmt::errorfln("TODO: substitute")!;
+ case commandtype::COPY =>
+ fmt::errorfln("TODO: copy")!;
+ case commandtype::UNDO =>
+ fmt::errorfln("TODO: undo")!;
+ case commandtype::GLOBAL_INVERSE =>
+ fmt::errorfln("TODO: global_inverse")!;
+ case commandtype::GLOBAL_INVERSE_INTERACTIVE =>
+ fmt::errorfln("TODO: global_inverse_interactive")!;
+ case commandtype::WRITE =>
+ const (a, b) = get_range(
+ lns,
+ addr_linenum(s, 1)!,
+ addr_lastline(s),
+ );
+ cmd_write(s, s.cmd.arg, a, b);
+ case commandtype::LINE_NUMBER =>
+ const n = get_linenum(lns, addr_lastline(s));
+ cmd_linenumber(s, n);
+ case commandtype::SHELL_ESCAPE =>
+ fmt::errorfln("TODO: shell_escape")!;
+ case commandtype::NULL =>
+ const n = get_linenum(lns, if (len(lns) == 0) {
+ yield addr_nextline(s, cursor);
+ } else {
+ yield cursor;
+ });
+ cmd_print(s, n, n);
+
+ case commandtype::DEBUG_NUMBER =>
+ dumpbuffer(&s.buf);
+ case =>
+ fmt::errorfln("DEBUG: Unknown command")!;
+ };
+};
+
+fn get_range(lns: []size, a: size, b: size) (size, size) = {
+ if (len(lns) == 0) {
+ return (a, b);
+ } else if (len(lns) == 1) {
+ return (lns[0], lns[0]);
+ } else {
+ // fmt::errorfln("DEBUG: ({}, {})", lns[len(lns)-2], lns[len(lns)-1])!;
+ const (a, b) = (lns[len(lns)-2], lns[len(lns)-1]);
+ return (a, b);
+ };
+};
+
+fn get_linenum(lns: []size, n: size) size = {
+ if (len(lns) == 0) {
+ return n;
} else {
- s.buffer.filename = file;
+ return lns[len(lns)-1];
+ };
+};
+
+fn assert_noaddrs(s: *session, lns: []size) bool = {
+ if (len(lns) != 0) {
+ errormsg(s, "Unexpected address");
+ return false;
+ };
+ return true;
+};
+
+fn cmd_prompt(s: *session) void = {
+ s.promptmode = !s.promptmode;
+};
+
+fn cmd_linenumber(s: *session, n: size) void = {
+ fmt::println(n)!;
+};
+
+fn cmd_help(s: *session) void = {
+ if (s.lasterror != "") {
+ fmt::println(s.lasterror)!;
+ };
+};
+
+fn cmd_helpmode(s: *session) void = {
+ s.helpmode = !s.helpmode;
+ if (s.helpmode) {
+ cmd_help(s);
+ };
+};
+
+fn cmd_print(s: *session, a: size, b: size) void = {
+ for (let n = a; n <= b; n += 1) {
+ fmt::println(s.buf.lines[n].text)!;
+ };
+ s.buf.cursor = b;
+};
+
+fn cmd_number(s: *session, a: size, b: size) void = {
+ for (let n = a; n <= b; n += 1) {
+ fmt::printfln("{}\t{}", n, s.buf.lines[n].text)!;
+ };
+ s.buf.cursor = b;
+};
+
+fn cmd_filename(s: *session, filename: str) void = {
+ if (len(filename) != 0) {
+ s.buf.filename = filename;
+ };
+ if (len(s.buf.filename) == 0) {
+ return errormsg(s, "No current filename");
+ };
+ fmt::println(s.buf.filename)!;
+};
+
+fn cmd_write(s: *session, filename: str, a: size, b: size) void = {
+ const fname = if (len(filename) != 0) {
+ s.buf.filename = filename;
+ yield filename;
+ } else {
+ yield if (len(s.buf.filename) != 0) {
+ yield s.buf.filename;
+ } else {
+ errormsg(s, "No current filename");
+ return;
+ };
};
- const file = s.buffer.filename as str;
- const h = match (os::open(file)) {
+ const h = match (os::open(fname)) {
+ case let err: fs::error =>
+ yield match (err) {
+ case errors::noentry =>
+ yield match (os::create(fname, 0o644)) {
+ case let h: io::file =>
+ yield h: io::handle;
+ case let err: fs::error =>
+ return errormsg(s, fs::strerror(err));
+ };
+ case =>
+ return errormsg(s, fs::strerror(err));
+ };
+ case let h: io::file =>
+ yield h: io::handle;
+ };
+ defer io::close(h)!;
+
+ const sz = buf_write(&s.buf, h, a, b);
+ if (!s.suppressmode) {
+ fmt::println(sz)!;
+ };
+};
+
+fn cmd_read(s: *session, filename: str, a: size) void = {
+ const fname = if (len(filename) != 0) {
+ s.buf.filename = filename;
+ yield filename;
+ } else {
+ yield if (len(s.buf.filename) != 0) {
+ yield s.buf.filename;
+ } else {
+ errormsg(s, "No current filename");
+ return;
+ };
+ };
+
+ const h = match (os::open(fname)) {
+ case let err: fs::error =>
+ return errormsg(s, fs::strerror(err));
+ case let h: io::file =>
+ yield h: io::handle;
+ };
+ defer io::close(h)!;
+
+ const (sz, _) = buf_read(&s.buf, h, a);
+ if (!s.suppressmode) {
+ fmt::println(sz)!;
+ };
+ s.buf.cursor = len(s.buf.lines) - 1;
+};
+
+fn cmd_edit(s: *session, filename: str) void = {
+ if (s.buf.modified && !s.warned) {
+ errormsg(s, "Warning: buffer modified");
+ s.warned = true;
+ return;
+ };
+
+ const fname = if (len(filename) != 0) {
+ s.buf.filename = filename;
+ yield filename;
+ } else {
+ yield if (len(s.buf.filename) != 0) {
+ yield s.buf.filename;
+ } else {
+ errormsg(s, "No current filename");
+ return;
+ };
+ };
+
+ const h = match (os::open(fname)) {
case let err: fs::error =>
return errormsg(s, fs::strerror(err));
case let h: io::file =>
@@ -21,9 +283,21 @@ fn cmd_edit(s: *session, file: (void | str)) void = {
};
defer io::close(h)!;
- buf_deleteall(&s.buffer);
- const sz = buf_read(&s.buffer, h, 0);
+ buf_deleteall(&s.buf);
+ const (sz, _) = buf_read(&s.buf, h, 0);
if (!s.suppressmode) {
fmt::println(sz)!;
};
+ s.buf.cursor = len(s.buf.lines) - 1;
+};
+
+fn cmd_delete(s: *session, filename: str, a: size, b: size) void = {
+ buf_delete(&s.buf, a, b);
+ s.buf.cursor = if (len(s.buf.lines) == 1) {
+ yield 0;
+ } else if (len(s.buf.lines) == a) {
+ yield a - 1;
+ } else {
+ yield a;
+ };
};
diff --git a/main.ha b/main.ha
@@ -1,15 +1,26 @@
use bufio;
+use encoding::utf8;
use fmt;
use getopt;
use io;
use os;
+use strings;
type session = struct {
- buffer: buffer,
+ buf: buffer,
+ mode: mode,
+ cmd: command,
helpmode: bool,
+ lasterror: str,
suppressmode: bool,
promptmode: bool,
prompt: str,
+ warned: bool,
+};
+
+type mode = enum {
+ COMMAND,
+ INPUT,
};
export fn main() void = {
@@ -22,13 +33,14 @@ export fn main() void = {
const cmd = getopt::parse(os::args, help...);
defer getopt::finish(&cmd);
- const s = session{
- buffer = buffer{
- filename = void,
- lines = [ alloc(line{ ... }) ],
+ const s = session {
+ buf = buffer {
+ lines = [ alloc(line { ... }) ],
trash = [],
...
},
+ cmd = command { ... },
+ prompt = "*",
...
};
@@ -54,32 +66,53 @@ export fn main() void = {
case "" =>
fmt::fatal("Invalid filename ''");
case =>
- s.buffer.filename = cmd.args[0];
+ s.buf.filename = cmd.args[0];
};
};
- if (s.buffer.filename is str) {
- cmd_edit(&s, s.buffer.filename as str);
+ if (len(s.buf.filename) != 0) {
+ cmd_edit(&s, s.buf.filename);
};
for (true) :repl {
- fmt::error(s.prompt)!;
+ if (s.promptmode) {
+ fmt::error(s.prompt)!;
+ };
const rawline = match (bufio::scanline(os::stdin)) {
- case let rawline: []u8 =>
- yield rawline;
+ case let bs: []u8 =>
+ yield bs;
case io::EOF =>
fmt::println()!;
break;
- case =>
- abort();
+ case let err: io::error =>
+ fmt::fatal(io::strerror(err));
};
defer free(rawline);
- const input = fmt::bsprint(rawline);
+ const input = match (strings::fromutf8(rawline)) {
+ case let s: str =>
+ yield s;
+ case encoding::utf8::invalid =>
+ fmt::errorln("Error: Invalid UTF-8 input")!;
+ continue;
+ };
+
+ switch (s.mode) {
+ case mode::COMMAND =>
+ if (parse(&s.cmd, input)) {
+ execute(&s);
+ };
+ case mode::INPUT =>
+ if (input == ".") {
+ s.mode = mode::COMMAND;
+ continue;
+ };
+ append(s.cmd.input, input);
+ };
};
- dumpbuffer(&s.buffer);
+ // dumpbuffer(&s.buf);
};
@noreturn fn exit_usage(help: []getopt::help) void = {
@@ -87,9 +120,10 @@ export fn main() void = {
os::exit(1);
};
-fn errormsg(s: *session, msgfmt: str, args: fmt::field...) void = {
+fn errormsg(s: *session, msg: str) void = {
fmt::errorln('?')!;
+ s.lasterror = msg;
if (s.helpmode) {
- fmt::errorfln(msgfmt)!;
+ fmt::errorfln(msg)!;
};
};
diff --git a/parse.ha b/parse.ha
@@ -0,0 +1,352 @@
+use fmt;
+
+use ascii;
+use strconv;
+use strings;
+
+type command = struct {
+ addrs: []address,
+ cmdtype: commandtype,
+ suffix: suffix,
+ arg: str,
+ input: []str,
+ subcmds: []command,
+};
+
+type commandtype = enum {
+ APPEND,
+ CHANGE,
+ DELETE,
+ EDIT,
+ EDIT_FORCED,
+ FILENAME,
+ GLOBAL,
+ GLOBAL_INTERACTIVE,
+ HELP,
+ HELPMODE,
+ INSERT,
+ JOIN,
+ MARK,
+ LIST,
+ MOVE,
+ NUMBER,
+ PRINT,
+ PROMPT,
+ QUIT,
+ QUIT_FORCED,
+ READ,
+ SUBSTITUTE,
+ COPY,
+ UNDO,
+ GLOBAL_INVERSE,
+ GLOBAL_INVERSE_INTERACTIVE,
+ WRITE,
+ LINE_NUMBER,
+ SHELL_ESCAPE,
+ NULL,
+
+ DEBUG_NUMBER,
+};
+
+type suffix = enum {
+ NONE,
+ LIST,
+ NUMBER,
+ PRINT,
+};
+
+// Parses inputted commands. Returns true on terminal input.
+fn parse(cmd: *command, input: str) bool = {
+ if (len(input) != 1) {
+ void;
+ };
+
+ const iter = strings::iter(input);
+
+ cmd.addrs = scan_addrs(&iter);
+ cmd.cmdtype = scan_cmdtype(&iter);
+ cmd.arg = scan_arg(&iter);
+
+ return true;
+};
+
+fn scan_addrs(iter: *strings::iterator) []address = {
+ let addrs: []address = [];
+ let specialfirst = false;
+
+ scan_blanks(iter);
+ match (strings::next(iter)) {
+ case void =>
+ return addrs;
+ case let r: rune =>
+ switch (r) {
+ case ',' =>
+ append(addrs, address {
+ addrtype = 1z,
+ lineoffset = 0,
+ });
+ specialfirst = true;
+ case ';' =>
+ append(addrs, address {
+ addrtype = currentline,
+ lineoffset = 0,
+ });
+ specialfirst = true;
+ case =>
+ strings::prev(iter);
+ };
+ };
+
+ for (true) {
+ match (scan_addr(iter)) {
+ case void =>
+ if (specialfirst) {
+ append(addrs, address {
+ addrtype = lastline,
+ lineoffset = 0,
+ });
+ } else if (len(addrs) > 0) {
+ const prevaddr = addrs[len(addrs)-1];
+ append(addrs, prevaddr);
+ } else {
+ break;
+ };
+ case let addr: address =>
+ append(addrs, addr);
+ };
+
+ specialfirst = false;
+
+ scan_blanks(iter);
+ match (strings::next(iter)) {
+ case void =>
+ break;
+ case let r: rune =>
+ switch (r) {
+ case ',', ';' =>
+ void;
+ case =>
+ strings::prev(iter);
+ break;
+ };
+ };
+ };
+
+ // fmt::errorfln("DEBUG: scan_addrs() len(addrs)={}", len(addrs))!;
+
+ return addrs;
+};
+
+fn scan_addr(iter: *strings::iterator) (address | void) = {
+ scan_blanks(iter);
+ let r = match (strings::next(iter)) {
+ case void =>
+ return void;
+ case let r: rune =>
+ yield r;
+ };
+
+ // fmt::errorfln("DEBUG: scan_addr() r={}", r)!;
+
+ const addrtype: (addresstype | void) = if (r == '.') {
+ yield currentline;
+ } else if (r == '$') {
+ yield lastline;
+ } else if (ascii::isdigit(r)) {
+ strings::prev(iter);
+ yield scan_uint(iter): size;
+ } else if (r == '\'') {
+ yield scan_mark(iter);
+ } else if (r == '/') {
+ // const re = scan_regex(iter, r);j
+ // ...
+ abort("TODO: /regex/");
+ } else if (r == '?') {
+ // const re = scan_regex(iter, r);j
+ // ...
+ abort("TODO: ?regex?");
+ } else {
+ strings::prev(iter);
+ yield void;
+ };
+
+ const offs = scan_offsets(iter);
+
+ const addrtype: addresstype = match (addrtype) {
+ case void =>
+ yield if (len(offs) == 0) {
+ return void;
+ } else {
+ yield currentline;
+ };
+ case =>
+ yield addrtype as addresstype;
+ };
+
+ let addr = address {
+ addrtype = addrtype,
+ lineoffset = 0,
+ };
+
+ for (let i = 0z; i < len(offs); i += 1) {
+ addr.lineoffset += offs[i];
+ };
+
+ return addr;
+};
+
+fn scan_offsets(iter: *strings::iterator) []int = {
+ let offs: []int = [];
+
+ for (true) {
+ scan_blanks(iter);
+
+ match (strings::next(iter)) {
+ case void =>
+ return offs;
+ case let r: rune =>
+ if (r == '+') {
+ append(offs, scan_offset(iter));
+ } else if (r == '-') {
+ append(offs, scan_offset(iter));
+ } else if (ascii::isdigit(r)) {
+ strings::prev(iter);
+ append(offs, scan_uint(iter): int);
+ } else {
+ strings::prev(iter);
+ break;
+ };
+ };
+ };
+
+ return offs;
+};
+
+fn scan_offset(iter: *strings::iterator) int = {
+ match (strings::next(iter)) {
+ case void =>
+ return 1;
+ case let r: rune =>
+ strings::prev(iter);
+ if (ascii::isdigit(r)) {
+ return scan_uint(iter): int;
+ } else {
+ return 1;
+ };
+ };
+};
+
+fn scan_cmdtype(iter: *strings::iterator) commandtype = {
+ let r = match (strings::next(iter)) {
+ case void =>
+ return commandtype::NULL;
+ case let r: rune =>
+ yield r;
+ };
+
+ switch (r) {
+ case '=' => return commandtype::LINE_NUMBER;
+ case 'E' => return commandtype::EDIT_FORCED;
+ case 'G' => return commandtype::GLOBAL_INTERACTIVE;
+ case 'H' => return commandtype::HELPMODE;
+ case 'P' => return commandtype::PROMPT;
+ case 'Q' => return commandtype::QUIT_FORCED;
+ case 'V' => return commandtype::GLOBAL_INVERSE_INTERACTIVE;
+ case 'a' => return commandtype::APPEND;
+ case 'c' => return commandtype::CHANGE;
+ case 'd' => return commandtype::DELETE;
+ case 'e' => return commandtype::EDIT;
+ case 'f' => return commandtype::FILENAME;
+ case 'g' => return commandtype::GLOBAL;
+ case 'h' => return commandtype::HELP;
+ case 'i' => return commandtype::INSERT;
+ case 'j' => return commandtype::JOIN;
+ case 'k' => return commandtype::MARK;
+ case 'l' => return commandtype::LIST;
+ case 'm' => return commandtype::MOVE;
+ case 'n' => return commandtype::NUMBER;
+ case 'p' => return commandtype::PRINT;
+ case 'q' => return commandtype::QUIT;
+ case 'r' => return commandtype::READ;
+ case 's' => return commandtype::SUBSTITUTE;
+ case 't' => return commandtype::COPY;
+ case 'u' => return commandtype::UNDO;
+ case 'v' => return commandtype::GLOBAL_INVERSE;
+ case 'w' => return commandtype::WRITE;
+
+ case 'b' => return commandtype::DEBUG_NUMBER;
+ case => abort("Invalid command");
+ };
+};
+
+
+fn scan_arg(iter: *strings::iterator) str = {
+ let rs: []rune = [];
+ for (true) {
+ match (strings::next(iter)) {
+ case void =>
+ break;
+ case let r: rune =>
+ append(rs, r);
+ };
+ };
+ return strings::trim(strings::fromrunes(rs));
+};
+
+fn scan_mark(iter: *strings::iterator) rune = {
+ match (strings::next(iter)) {
+ case void =>
+ abort();
+ case let r: rune =>
+ if (ascii::isalpha(r)) { // TODO: cover all mark chars
+ return r;
+ } else {
+ abort();
+ };
+ };
+};
+
+fn scan_uint(iter: *strings::iterator) uint = {
+ let num: []u8 = [];
+ defer free(num);
+ for (true) {
+ let r = match (strings::next(iter)) {
+ case void =>
+ break;
+ case let r: rune =>
+ yield r;
+ };
+
+ if (ascii::isdigit(r)) {
+ append(num, r: u32: u8);
+ } else {
+ strings::prev(iter);
+ break;
+ };
+ };
+
+ if (len(num) == 0) {
+ return 0;
+ };
+
+ match (strconv::stou(strings::fromutf8(num)!)) {
+ case (strconv::invalid | strconv::overflow) =>
+ abort("Invalid");
+ case let u: uint =>
+ return u;
+ };
+};
+
+fn scan_blanks(iter: *strings::iterator) void = {
+ for (true) {
+ match (strings::next(iter)) {
+ case void =>
+ return;
+ case let r: rune =>
+ if (!ascii::isblank(r)) {
+ strings::prev(iter);
+ return;
+ };
+ };
+ };
+};