ed

[hare] The standard editor
Log | Files | Refs | README | LICENSE

commit 9508bae0bfd364cc1e60305b9c310dc836db18c9
parent af82d6b3db88a3af3281be29992904dd92d3d313
Author: Byron Torres <b@torresjrjr.com>
Date:   Sat,  6 Jan 2024 11:22:39 +0000

progress; parse input stream; struct{} style

Diffstat:
Maddress.ha | 4++--
Mbuffer.ha | 6+++---
Mcommand.ha | 47+++++++++++++++++++++++++----------------------
Mmain.ha | 75++++++++++++++++++++-------------------------------------------------------
Mparse.ha | 124++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
5 files changed, 138 insertions(+), 118 deletions(-)

diff --git a/address.ha b/address.ha @@ -1,6 +1,6 @@ use regex; -type Address = struct { +type Address = struct{ addrform: AddressForm, lineoffset: int, setcurrentline: bool, @@ -15,7 +15,7 @@ type CurrentLine = void; type LastLine = void; // Regular expression search. /forward/ is true, ?backward? is false. -type RegexAddr = struct { +type RegexAddr = struct{ expr: str, direction: bool, }; diff --git a/buffer.ha b/buffer.ha @@ -4,7 +4,7 @@ use fmt; use io; use strings; -type Buffer = struct { +type Buffer = struct{ filename: str, lines: []*Line, trash: []*Line, @@ -12,7 +12,7 @@ type Buffer = struct { modified: bool, }; -type Line = struct { +type Line = struct{ text: str, mark: rune, globalmark: bool, @@ -65,7 +65,7 @@ fn buf_read(buf: *Buffer, src: io::handle, a: size) ((size, size) | io::error | sz += len(bytes) + 1; // TODO: handle newlines better const text = strings::fromutf8(bytes)?; - append(ls, alloc(Line { text = text, ... })); + append(ls, alloc(Line{ text = text, ... })); }; insert(buf.lines[a + 1], ls...); diff --git a/command.ha b/command.ha @@ -9,7 +9,7 @@ use regex; use strings; use memio; -type Command = struct { +type Command = struct{ addrs: []Address, linenums: []size, cmdname: rune, @@ -17,13 +17,13 @@ type Command = struct { arg1: str, arg2: str, arg3: str, - input: []str, + textinput: []str, subcmds: []Command, }; type CommandFn = *fn(*Session, *Command) (void | Error); -type PrintMode = enum { +type PrintMode = enum{ NONE, LIST, NUMBER, @@ -62,7 +62,7 @@ fn command_finish(cmd: *Command) void = { debug("command_finish(): free(cmd.arg3)"); free(cmd.arg3); debug("command_finish(): delete(cmd.input[..])"); - delete(cmd.input[..]); + delete(cmd.textinput[..]); debug("command_finish(): END"); // TODO: free other fields? // TODO: make a separate "fn command_clear()" ? probably not @@ -107,13 +107,13 @@ fn lookupcmd(name: rune) CommandFn = { fn cmd_append(s: *Session, cmd: *Command) (void | Error) = { const n = get_linenum(cmd.linenums, s.buf.cursor); - for (let i = 0z; i < len(cmd.input); i += 1) { - const l = alloc(Line { text = cmd.input[i], ... }); + for (let i = 0z; i < len(cmd.textinput); i += 1) { + const l = alloc(Line{ text = cmd.textinput[i], ... }); debug("cmd_append(): l.text={}", l.text); buf_insert(s.buf, n + 1 + i, l); }; - s.buf.cursor = n + len(cmd.input); + s.buf.cursor = n + len(cmd.textinput); }; fn cmd_change(s: *Session, cmd: *Command) (void | Error) = { @@ -132,20 +132,20 @@ fn cmd_change(s: *Session, cmd: *Command) (void | Error) = { buf_delete(s.buf, a, b); - for (let i = 0z; i < len(cmd.input); i += 1) { - const l = alloc(Line { text = cmd.input[i], ... }); + for (let i = 0z; i < len(cmd.textinput); i += 1) { + const l = alloc(Line{ text = cmd.textinput[i], ... }); debug("cmd_append(): l.text={}", l.text); buf_insert(s.buf, a + i, l); }; - s.buf.cursor = if (len(cmd.input) == 0) { + s.buf.cursor = if (len(cmd.textinput) == 0) { yield if (len(s.buf.lines) == a) { yield a - 1; } else { yield a; }; } else { - yield a + len(cmd.input) - 1; + yield a + len(cmd.textinput) - 1; }; }; @@ -206,6 +206,7 @@ fn cmd_edit_forced(s: *Session, cmd: *Command) (void | Error) = void; fn cmd_filename(s: *Session, cmd: *Command) (void | Error) = { assert_noaddrs(s, cmd.linenums)?; + if (cmd.arg1 != "") { //debug("cmd_filename(): cmd.arg1 != 0"); //free(s.buf.filename); @@ -213,9 +214,11 @@ fn cmd_filename(s: *Session, cmd: *Command) (void | Error) = { debug("cmd_filename(): s.buf.filename = dup(cmd.arg1)"); s.buf.filename = strings::dup(cmd.arg1); }; + if (s.buf.filename == "") { return NoFilename; }; + fmt::println(s.buf.filename)!; }; @@ -264,7 +267,7 @@ fn cmd_global_interative(s: *Session, cmd: *Command) (void | Error) = { fmt::println(line.text)!; s.buf.cursor = n; - let cmd = Command { ... }; + let cmd = Command{ ... }; // const rawinput = match (bufio::read_line(os::stdin)?) { // case let bs: []u8 => @@ -276,7 +279,7 @@ fn cmd_global_interative(s: *Session, cmd: *Command) (void | Error) = { // const input = strings::fromutf8(rawinput)?; const input = scanline(s)?; - parse(&cmd, input)?; + let cmd = parse(s.input)!; execute(s, &cmd)?; // TODO: test if line was modified. @@ -305,17 +308,17 @@ fn cmd_insert(s: *Session, cmd: *Command) (void | Error) = { const n = get_linenum(cmd.linenums, s.buf.cursor); const n = if (n == 0) 1z else n; - for (let i = 0z; i < len(cmd.input); i += 1) { - const l = alloc(Line { text = cmd.input[i], ... }); + for (let i = 0z; i < len(cmd.textinput); i += 1) { + const l = alloc(Line{ text = cmd.textinput[i], ... }); debug("cmd_append(): l.text={}", l.text); buf_insert(s.buf, n + i, l); }; - s.buf.cursor = if (len(cmd.input) == 0) { - yield n; - } else { - yield n + len(cmd.input) - 1; - }; + s.buf.cursor = + if (len(cmd.textinput) == 0) + n + else + n + len(cmd.textinput) - 1; }; fn cmd_join(s: *Session, cmd: *Command) (void | Error) = { @@ -342,7 +345,7 @@ fn cmd_join(s: *Session, cmd: *Command) (void | Error) = { }; const newtext = strings::concat(ls...); - const newline = alloc(Line { + const newline = alloc(Line{ text = newtext, mark = mark, ... @@ -544,7 +547,7 @@ fn cmd_substitute(s: *Session, cmd: *Command) (void | Error) = { }; memio::concat(&new, strings::sub(old, rbound, strings::end))!; - let newline = alloc(Line { + let newline = alloc(Line{ text = strings::dup(memio::string(&new)!), ... }); diff --git a/main.ha b/main.ha @@ -9,7 +9,7 @@ use regex; use strings; use types; -type Session = struct { +type Session = struct{ input: *bufio::scanner, buf: *Buffer, mode: InputMode, @@ -22,11 +22,6 @@ type Session = struct { prev_shcmd: (str | void), }; -type InputMode = enum { - COMMAND, - TEXT, -}; - type Error = !(...InteractionError | ParseError | CmdError); def help: [_]getopt::help = [ @@ -43,14 +38,14 @@ export fn main() void = { const input = bufio::newscanner(os::stdin, types::SIZE_MAX); defer bufio::finish(&input); - const buffer = Buffer { - lines = *alloc([ alloc(Line { ... }) ]), + const buffer = Buffer{ + lines = *alloc([ alloc(Line{ ... }) ]), trash = *alloc([]: []*Line), ... }; defer buffer_finish(&buffer); - let s = Session { + let s = Session{ input = &input, buf = &buffer, prompt = "*", @@ -71,11 +66,10 @@ export fn main() void = { }; }; - if (len(main_cmd.args) > 1) { + if (len(main_cmd.args) > 1) exit_usage(); - }; - if (len(main_cmd.args) == 1) { + if (len(main_cmd.args) == 1) switch (main_cmd.args[0]) { case "-" => fmt::fatal("Invalid filename '-'"); @@ -84,60 +78,31 @@ export fn main() void = { case => s.buf.filename = strings::dup(main_cmd.args[0]); }; - }; if (len(s.buf.filename) != 0) { - let cmd = Command { ... }; + let cmd = Command{ ... }; cmd.arg1 = strings::dup(s.buf.filename); cmd_edit(&s, &cmd): void; // TODO: handle error? }; - let cmd = Command { ... }; - for (true) :repl { - if (s.promptmode && s.mode == InputMode::COMMAND) { + if (s.promptmode) fmt::error(s.prompt)!; - }; - const inputstr = match (bufio::scan_line(s.input)) { - case let s: const str => - yield s; - case io::EOF => - fmt::println()!; - break; - case let err: io::error => - fmt::fatal(io::strerror(err)); + const cmd = match (parse(&input)) { + case let cmd: Command => + yield cmd; + case let err: ParseError => + errormsg(&s, strerror(err)); + continue; }; - switch (s.mode) { - case InputMode::COMMAND => - const execnow = match (parse(&cmd, inputstr)) { - case let err: ParseError => - errormsg(&s, strerror(err)); - continue; - case let b: bool => - yield b; - }; - if (execnow) { - // TODO: handle all ": void"s - execute(&s, &cmd): void; - debug("main(): before command_finish()"); - command_finish(&cmd); - debug("main(): after command_finish()"); - } else { - s.mode = InputMode::TEXT; - continue; - }; - case InputMode::TEXT => - if (inputstr == ".") { - execute(&s, &cmd): void; - command_finish(&cmd); - s.mode = InputMode::COMMAND; - continue; - }; - debug("repl: input={}", inputstr); - append(cmd.input, strings::dup(inputstr)); - }; + // TODO: handle all ": void"s + execute(&s, &cmd): void; + + debug("main(): before command_finish()"); + command_finish(&cmd); + debug("main(): after command_finish()"); }; // dumpbuffer(&s.buf); diff --git a/parse.ha b/parse.ha @@ -1,9 +1,15 @@ use ascii; +use bufio; use io; use regex; use strconv; use strings; +type InputMode = enum{ + COMMAND, + TEXT, +}; + type ParseError = !( UnknownCommand | UnexpectedSuffix @@ -29,73 +35,119 @@ type InvalidDelimiter = !void; type ExpectedDelimiter = !void; // Parses inputted commands. Returns true when command is ready. -fn parse(cmd: *Command, input: str) (bool | ParseError) = { - const iter = strings::iter(input); +fn parse(input: *bufio::scanner) (Command | ParseError | InteractionError) = { + let inputline = match (bufio::scan_line(input)?) { + case let s: const str => + yield s; + case io::EOF => + return Command{ cmdname = 'q', ... }; + }; + let iter = strings::iter(inputline); + let cmd = Command{ ... }; + cmd.addrs = scan_addrs(&iter); cmd.cmdname = scan_cmdname(&iter)?; + parse_cmdargs(&cmd, &iter)?; switch (cmd.cmdname) { + case => void; + case 'a', 'c', 'i' => + for (true) { + let inputline = match (bufio::scan_line(input)?) { + case let s: const str => + yield s; + case io::EOF => + break; + }; + + if (inputline == ".") + break; + + append(cmd.textinput, inputline); + }; + case 'q', 'v' => + if (!strings::hassuffix(cmd.arg2, '\\')) + yield; + + for (true) { + let inputstr = match (bufio::scan_line(input)?) { + case let s: const str => + yield s; + case io::EOF => + break; + }; + + // TODO: parse and append to .subcmds ? + append(cmd.textinput, inputstr); + }; + }; + + return cmd; +}; + +fn parse_cmdargs(cmd: *Command, iter: *strings::iterator) (bool | ParseError) = { + switch (cmd.cmdname) { // [ ] case '\x00' => return true; // (q|Q) case 'q', 'Q' => - scan_end_assert(&iter)?; + scan_end_assert(iter)?; return true; // .[ <file>] case 'e', 'E', 'f', 'r', 'w' => - if (scan_blanks(&iter) == 0) { - match (strings::next(&iter)) { + if (scan_blanks(iter) == 0) { + match (strings::next(iter)) { case let r: rune => return r: UnexpectedSuffix; case void => return true; }; } else { - cmd.arg1 = scan_rest(&iter); + cmd.arg1 = scan_rest(iter); return true; }; // k<x> case 'k' => - match (strings::next(&iter)) { + match (strings::next(iter)) { case let r: rune => cmd.arg1 = strings::fromrunes([r]); case void => return ExpectedMark; }; - scan_end_assert(&iter)?; + scan_end_assert(iter)?; return true; // !<shellcmd> case '!' => - cmd.arg1 = scan_rest(&iter); + cmd.arg1 = scan_rest(iter); return true; // .[s] where 's' is '(l|n|p)' case 'd', 'h', 'H', 'j', 'l', 'n', 'p', 'P', 'u', '=' => - cmd.printmode = scan_suffix(&iter); - scan_end_assert(&iter)?; + cmd.printmode = scan_suffix(iter); + scan_end_assert(iter)?; return true; // .[s] case 'a', 'c', 'i' => - cmd.printmode = scan_suffix(&iter); - scan_end_assert(&iter)?; + cmd.printmode = scan_suffix(iter); + scan_end_assert(iter)?; return false; // .[s][ ]<addr> case 'm', 't' => - cmd.printmode = scan_suffix(&iter); - cmd.arg1 = scan_rest(&iter); + cmd.printmode = scan_suffix(iter); + cmd.arg1 = scan_rest(iter); return true; // ./<regex>[/] where delimiter '/' is arbitrary case 'G', 'V' => - const delim = match (strings::next(&iter)) { + const delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -105,14 +157,14 @@ fn parse(cmd: *Command, input: str) (bool | ParseError) = { yield r; }; }; - cmd.arg1 = scan_item(&iter, delim); - strings::next(&iter); // scan delimiter if exists - scan_end_assert(&iter)?; + cmd.arg1 = scan_item(iter, delim); + strings::next(iter); // scan delimiter if exists + scan_end_assert(iter)?; return true; // ./<regex>/<cmdlist...> case 'g', 'v' => - const delim = match (strings::next(&iter)) { + const delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -122,17 +174,17 @@ fn parse(cmd: *Command, input: str) (bool | ParseError) = { yield r; }; }; - cmd.arg1 = scan_item(&iter, delim); - strings::next(&iter); // scan delimiter if exists - cmd.arg2 = scan_rest(&iter); - if (strings::prev(&iter) as rune == '\\') { + cmd.arg1 = scan_item(iter, delim); + strings::next(iter); // scan delimiter if exists + cmd.arg2 = scan_rest(iter); // TODO: append to .subcmds? + if (strings::prev(iter) as rune == '\\') { return false; }; return true; // s/<regex>/[<replace>[/[<flags>]]] case 's' => - const delim = match (strings::next(&iter)) { + const delim = match (strings::next(iter)) { case void => return ExpectedArgument; case let r: rune => @@ -142,19 +194,19 @@ fn parse(cmd: *Command, input: str) (bool | ParseError) = { yield r; }; }; - cmd.arg1 = scan_item(&iter, delim); - match (strings::next(&iter)) { + cmd.arg1 = scan_item(iter, delim); + match (strings::next(iter)) { case rune => void; case void => return ExpectedDelimiter; }; - cmd.arg2 = scan_item(&iter, delim); - match (strings::next(&iter)) { + cmd.arg2 = scan_item(iter, delim); + match (strings::next(iter)) { case rune => void; case void => return true; }; - cmd.arg3 = scan_rest(&iter); // TODO: scan properly here? + cmd.arg3 = scan_rest(iter); // TODO: scan properly here? return true; case => @@ -174,14 +226,14 @@ fn scan_addrs(iter: *strings::iterator) []Address = { switch (r) { case ',' => specialfirst = true; - append(addrs, Address { + append(addrs, Address{ addrform = 1z, lineoffset = 0, setcurrentline = false, }); case ';' => specialfirst = true; - append(addrs, Address { + append(addrs, Address{ addrform = CurrentLine, lineoffset = 0, setcurrentline = true, @@ -195,7 +247,7 @@ fn scan_addrs(iter: *strings::iterator) []Address = { let addr = match (scan_addr(iter)) { case void => yield if (specialfirst) { - yield Address { + yield Address{ addrform = LastLine, lineoffset = 0, ... @@ -258,14 +310,14 @@ fn scan_addr(iter: *strings::iterator) (Address | void) = { } else if (r == '\'') { yield scan_mark(iter); } else if (r == '/') { - const rad = RegexAddr { + const rad = RegexAddr{ expr = scan_item(iter, '/'), direction = true, }; strings::next(iter); yield rad; } else if (r == '?') { - const rad = RegexAddr { + const rad = RegexAddr{ expr = scan_item(iter, '?'), direction = false, }; @@ -289,7 +341,7 @@ fn scan_addr(iter: *strings::iterator) (Address | void) = { yield addrform as AddressForm; }; - let addr = Address { + let addr = Address{ addrform = addrform, lineoffset = 0, ...