9 files changed, 206 insertions(+), 665 deletions(-)
M README.md
M eval.ha
M main.ha
D nui.ha
M parse.ha
D ui.ha
D vt/cmd/vttest/main.ha
D vt/parser.ha
D vt/types.ha
M README.md => README.md +4 -1
@@ 39,7 39,9 @@ $ csq
```
# Installation
-At build time, a recent [Hare] toolchain is required.
+Dependencies:
+* A recent [Hare] toolchain
+* [madeline]
```shell-session
$ make
@@ 55,5 57,6 @@ $ git config format.subjectPrefix "PATCH csq"
```
[Hare]: https://harelang.org/documentation/install/
+[madeline]: https://sr.ht/~ecs/madeline
[archives]: https://lists.sr.ht/~yerinalexey/public-inbox
[git send-email]: https://git-send-email.io
M eval.ha => eval.ha +51 -19
@@ 12,30 12,62 @@ let factorials: [21]i64 = [0...];
};
};
-fn eval(u: *ui, e: *expr) (rational::number | noresult | invalid) = {
+type state = struct {
+ options: options,
+ results: [](rational::number | invalid),
+};
+
+fn init() state = {
+ return state {
+ options = default_options,
+ results = [],
+ };
+};
+
+fn eval_line(st: *state, line: str) (rational::number | noresult | invalid) = {
+ let expr = match (parse_expr(line)) {
+ case let e: *expr =>
+ yield e;
+ case invalid =>
+ append(st.results, invalid);
+ return invalid;
+ };
+ defer expr_free(expr);
+
+ match (eval(st, expr)) {
+ case let result: (rational::number | invalid) =>
+ append(st.results, result);
+ return result;
+ case noresult =>
+ append(st.results, invalid);
+ return noresult;
+ };
+};
+
+fn eval(st: *state, e: *expr) (rational::number | noresult | invalid) = {
match (*e) {
case let i: rational::number =>
return i;
case let r: reference =>
if (r == -1) {
- if (len(u.results) == 0) {
+ if (len(st.results) == 0) {
return invalid;
};
- return u.results[len(u.results) - 1];
+ return st.results[len(st.results) - 1];
};
const r = (r - 1): size;
- if (r >= len(u.results)) {
+ if (r >= len(st.results)) {
return invalid;
};
- return u.results[r];
+ return st.results[r];
case let s: sqrt_expr =>
- const s = eval(u, s)?;
+ const s = eval(st, s)?;
if (rational::sign(s) < 0) {
return invalid;
};
return rational::sqrt(s);
case let f: factorial_expr =>
- const f = eval(u, f)?;
+ const f = eval(st, f)?;
if (rational::sign(f) < 0 || f.denominator != 1) {
return invalid;
};
@@ 44,38 76,38 @@ fn eval(u: *ui, e: *expr) (rational::number | noresult | invalid) = {
};
return rational::num(factorials[f.numerator], 1);
case let s: negate_expr =>
- const s = eval(u, s)?;
+ const s = eval(st, s)?;
return rational::neg(s);
case let o: option_expr =>
const (name, value) = o;
match (value) {
case let value: *expr =>
- set_option(&u.options, name, eval(u, value)?)?;
+ set_option(&st.options, name, eval(st, value)?)?;
return noresult;
case =>
- return get_option(&u.options, name);
+ return get_option(&st.options, name);
};
case let o: op_expr =>
switch (o.op) {
case operation::ADD =>
- return rational::add(eval(u, o.lhs)?, eval(u, o.rhs)?);
+ return rational::add(eval(st, o.lhs)?, eval(st, o.rhs)?);
case operation::SUBTRACT =>
- return rational::sub(eval(u, o.lhs)?, eval(u, o.rhs)?);
+ return rational::sub(eval(st, o.lhs)?, eval(st, o.rhs)?);
case operation::MULTIPLY =>
- return rational::mul(eval(u, o.lhs)?, eval(u, o.rhs)?);
+ return rational::mul(eval(st, o.lhs)?, eval(st, o.rhs)?);
case operation::DIVIDE =>
- const rhs = eval(u, o.rhs)?;
+ const rhs = eval(st, o.rhs)?;
if (rhs.numerator == 0) {
return invalid;
};
- return rational::div(eval(u, o.lhs)?, rhs);
+ return rational::div(eval(st, o.lhs)?, rhs);
case operation::EXPONENT =>
- const lhs = eval(u, o.lhs)?;
- const rhs = eval(u, o.rhs)?;
+ const lhs = eval(st, o.lhs)?;
+ const rhs = eval(st, o.rhs)?;
return exponent(lhs, rhs)?;
case operation::MODULO =>
- const lhs = eval(u, o.lhs)?;
- const rhs = eval(u, o.rhs)?;
+ const lhs = eval(st, o.lhs)?;
+ const rhs = eval(st, o.rhs)?;
if (rational::sign(rhs) == 0) {
return invalid;
};
M main.ha => main.ha +148 -21
@@ 1,37 1,164 @@
-use errors;
+use bufio;
+use memio;
+use encoding::utf8;
use fmt;
use io;
+use made;
use os;
+use rational;
+use strings;
use unix::tty;
-use vt;
export fn main() void = {
- if (!tty::isatty(os::stdin_file)) {
- // Non-interactive mode
- const u = nui_init();
- defer nui_finish(&u);
+ let state = init();
- match (nui_run(&u)) {
- case => void;
+ if (tty::isatty(os::stdin_file) && tty::isatty(os::stdout_file)) {
+ match (run_interactive(&state)) {
+ case void => void;
+ case let err: made::error =>
+ fmt::fatal("Error:", made::strerror(err));
+ };
+ } else {
+ match (run_non_interactive(&state)) {
+ case void => void;
case let err: io::error =>
fmt::fatal("Error:", io::strerror(err));
+ case let err: utf8::invalid =>
+ fmt::fatal("Error:", utf8::strerror(err));
+ };
+ };
+};
+
+type context = struct {
+ mctx: made::context,
+ state: *state,
+};
+
+fn hint_preview(ctx: *made::context, line: []u8) str = {
+ let ctx = ctx: *context;
+ if (!ctx.state.options.preview) {
+ return "";
+ };
+
+ let line = strings::fromutf8(line)!;
+
+ let expr = match (parse_expr(line)) {
+ case let e: *expr =>
+ yield e;
+ case invalid =>
+ return "";
+ };
+ defer expr_free(expr);
+
+ if (!expr_pure(expr)) {
+ return "";
+ };
+
+ match (eval(ctx.state, expr)) {
+ case let result: rational::number =>
+ let buf = memio::dynamic();
+ memio::concat(&buf, " = ")!;
+ print_rational(&buf, ctx.state, result)!;
+ return memio::string(&buf)!;
+ case invalid =>
+ return " = invalid";
+ case noresult =>
+ return "";
+ };
+};
+
+fn run_interactive(st: *state) (void | made::error) = {
+ let ctx = context {
+ mctx = made::context {
+ complete = &made::complete_none,
+ hint = &hint_preview,
+ hist = &made::histmem(),
+ prompt = &made::prompt_string("> "),
+ split = made::split_default,
+ cfg = made::config_default("csq")?,
+ ...
+ },
+ state = st,
+ };
+ defer made::ctx_finish(&ctx)!;
+
+ for (true) {
+ match (made::line(&ctx)?) {
+ case let line: str =>
+ defer free(line);
+
+ // Since madeline outputs a newline character after
+ // pressing enter, it's necessary to clear that line so
+ // expression number, the expression and its result can
+ // be written on one line.
+ //
+ // ESC[F moves to the beginning of the previous line
+ // ESC[2K clears that line
+ //
+ // TODO: make this work for lines that exceed terminal
+ // width
+ fmt::print("\x1b[F" "\x1b[2K")?;
+
+ fmt::printf("({}) {}", len(st.results) + 1, line)?;
+ let empty = (len(strings::trim(line)) == 0);
+ if (!empty) {
+ match (eval_line(st, line)) {
+ case let n: rational::number =>
+ fmt::print(" = ")?;
+ print_rational(os::stdout, st, n)?;
+ case invalid =>
+ fmt::print(" = invalid")?;
+ case noresult => void;
+ };
+ };
+ fmt::println()?;
+ case io::EOF =>
+ break;
+ case void =>
+ // Ctrl-C
+ // See above for explaination of the sequence
+ fmt::print("\x1b[F" "\x1b[2K")?;
+ bufio::flush(os::stdout)?;
};
- return;
};
+};
- const u = match (ui_init()) {
- case let u: ui =>
- yield u;
- case let err: io::error =>
- fmt::fatal("I/O error:", io::strerror(err));
- case let err: errors::error =>
- fmt::fatal("Error:", errors::strerror(err));
+fn run_non_interactive(st: *state) (void | io::error | utf8::invalid) = {
+ for (let line => bufio::read_line(os::stdin)?) {
+ let line = strings::fromutf8(line)?;
+ match (eval_line(st, line)) {
+ case let n: rational::number =>
+ print_rational(os::stdout, st, n)?;
+ fmt::println()?;
+ case invalid =>
+ fmt::println("invalid")?;
+ case noresult => void;
+ };
};
- defer ui_finish(&u);
+};
- match (ui_run(&u)) {
- case => void;
- case let err: vt::error =>
- fmt::fatal("Terminal error:", vt::strerror(err));
+fn print_rational(
+ out: io::handle,
+ st: *state,
+ n: rational::number,
+) (size | io::error) = {
+ if (!st.options.mixed) {
+ return rational::fmt(out, n);
+ };
+
+ const mixed = n.numerator / n.denominator: i64;
+ n.numerator %= n.denominator: i64;
+ if (mixed == 0) {
+ return rational::fmt(out, n);
+ };
+ if (n.numerator == 0) {
+ return fmt::fprint(out, mixed);
+ };
+ if (rational::sign(n) > 0) {
+ return fmt::fprintf(out, "{} + {}/{}",
+ mixed, n.numerator, n.denominator);
+ } else {
+ return fmt::fprintf(out, "-({} + {}/{})",
+ -mixed, -n.numerator, n.denominator);
};
};
D nui.ha => nui.ha +0 -42
@@ 1,42 0,0 @@
-// nui = Non-interactive UI
-// Why did I name it that? No idea
-use bufio;
-use fmt;
-use io;
-use os;
-use rational;
-use strings;
-
-fn nui_init() ui = ui {
- term = os::stdout_file,
- options = default_options,
- ...
-};
-
-fn nui_finish(u: *ui) void = {
- free(u.results);
-};
-
-fn nui_run(u: *ui) (void | io::error) = {
- for (let line => bufio::read_line(os::stdin)?) {
- let line = strings::fromutf8(line)!;
-
- match (parse_expr(line)) {
- case let e: *expr =>
- match (eval(u, e)) {
- case let i: rational::number =>
- append(u.results, i);
- print_rational(u, i)?;
- fmt::fprintln(u.term)?;
- case noresult =>
- append(u.results, invalid);
- case invalid =>
- append(u.results, invalid);
- fmt::fprintln(u.term, "invalid")?;
- };
- case =>
- append(u.results, invalid);
- fmt::fprintln(u.term, "invalid")?;
- };
- };
-};
M parse.ha => parse.ha +3 -3
@@ 35,12 35,12 @@ fn expr_free(e: *expr) void = {
free(e);
};
-fn expr_void(e: *expr) bool = {
+fn expr_pure(e: *expr) bool = {
match (*e) {
case let opt: option_expr =>
- return opt.1 is *expr;
+ return !(opt.1 is *expr);
case =>
- return false;
+ return true;
};
};
D ui.ha => ui.ha +0 -302
@@ 1,302 0,0 @@
-use ascii;
-use errors;
-use fmt;
-use io;
-use math;
-use rational;
-use strings;
-use unix::tty;
-use vt;
-
-type ui = struct {
- term: io::file,
- termios: tty::termios,
- running: bool,
-
- options: options,
-
- results: [](rational::number | invalid),
- entry: []rune,
- entry_pos: size,
-};
-
-fn ui_init() (ui | io::error | errors::error) = {
- const term = match (tty::open()) {
- case let f: io::file =>
- yield f;
- case let err: tty::error =>
- return err: errors::error;
- };
- const termios = tty::termios_query(term)?;
- tty::makeraw(&termios)?;
- fmt::fprint(term, "> ")?;
-
- return ui {
- term = term,
- termios = termios,
- options = default_options,
- ...
- };
-};
-
-fn ui_finish(u: *ui) void = {
- fmt::fprint(u.term, "\x1b[2K\r")!;
- tty::termios_restore(&u.termios);
- io::close(u.term)!;
- free(u.entry);
- free(u.results);
-};
-
-fn ui_run(u: *ui) (void | vt::error) = {
- u.running = true;
- for (u.running) {
- ui_tick(u)?;
- };
-};
-
-fn ui_tick(u: *ui) (void | vt::error) = {
- const (key, modifiers) = match (vt::nextkey(u.term)) {
- case let k: vt::key =>
- yield k;
- case let err: vt::error =>
- match (err) {
- case vt::invalid =>
- return;
- case =>
- return err;
- };
- };
- match (key) {
- case let kk: vt::keycode =>
- switch (kk) {
- case vt::keycode::DELETE =>
- ui_entry_delete(u);
- case vt::keycode::BACKSPACE =>
- ui_entry_backspace(u);
- case vt::keycode::HOME =>
- ui_entry_home(u);
- case vt::keycode::END =>
- ui_entry_end(u);
- case vt::keycode::RIGHT =>
- if (modifiers & vt::modifiers::CONTROL != 0) {
- ui_entry_right_wordwise(u);
- } else {
- ui_entry_right(u);
- };
- case vt::keycode::LEFT =>
- if (modifiers & vt::modifiers::CONTROL != 0) {
- ui_entry_left_wordwise(u);
- } else {
- ui_entry_left(u);
- };
- case vt::keycode::ENTER =>
- append_expr(u)?;
- case => void;
- };
- case let r: rune =>
- if (modifiers & vt::modifiers::CONTROL != 0) {
- switch (r) {
- case 'd' =>
- u.running = false;
- case 'c' =>
- ui_entry_clear(u);
- case 'w' =>
- ui_entry_kill_word(u);
- case =>
- // TODO: ctrl+r(?)
- ui_entry_insert(u, 'C');
- ui_entry_insert(u, '-');
- ui_entry_insert(u, r);
- };
- } else if (ascii::isprint(r)) {
- ui_entry_insert(u, r);
- };
- };
-
- ui_entry_draw(u)?;
-};
-
-fn append_expr(u: *ui) (void | io::error) = {
- const s = strings::fromrunes(u.entry);
- defer free(s);
-
- ui_entry_clear(u);
-
- if (len(strings::trim(s)) == 0) {
- fmt::fprintln(u.term)?;
- return;
- };
-
- fmt::fprintf(u.term, "\r\x1b[2K({}) {}", len(u.results) + 1, s)?;
- match (parse_expr(s)) {
- case let e: *expr =>
- match (eval(u, e)) {
- case let i: rational::number =>
- append(u.results, i);
- fmt::fprint(u.term, " = ")?;
- print_rational(u, i)?;
- fmt::fprintln(u.term)?;
- case noresult =>
- append(u.results, invalid);
- fmt::fprintln(u.term)?;
- case invalid =>
- append(u.results, invalid);
- fmt::fprintfln(u.term, " = invalid")?;
- };
- case =>
- append(u.results, invalid);
- fmt::fprintfln(u.term, " = invalid")?;
- };
-};
-
-fn ui_entry_draw(u: *ui) (void | io::error) = {
- fmt::fprint(u.term, "\r\x1b[2K> ")?;
- for (let i = 0z; i < u.entry_pos; i += 1) {
- fmt::fprint(u.term, u.entry[i])?;
- };
- fmt::fprint(u.term, "\x1b7")?; // Save cursor
- for (let i = u.entry_pos; i < len(u.entry); i += 1) {
- fmt::fprint(u.term, u.entry[i])?;
- };
-
- if (u.options.preview) {
- const s = strings::fromrunes(u.entry);
- defer free(s);
- match (parse_expr(s)) {
- case let e: *expr =>
- defer expr_free(e);
- if (expr_void(e)) yield;
-
- fmt::fprint(u.term, " \x1b[33m = ")?;
- match (eval(u, e)) {
- case let i: rational::number =>
- print_rational(u, i)?;
- case noresult => void;
- case invalid =>
- fmt::fprintf(u.term, "invalid")?;
- };
- fmt::fprint(u.term, "\x1b[0m")?;
- case invalid => void;
- };
- };
-
- // Restore cursor
- fmt::fprint(u.term, "\x1b8")?;
-};
-
-fn ui_entry_insert(u: *ui, r: rune) void = {
- insert(u.entry[u.entry_pos], r);
- u.entry_pos += 1;
-};
-
-fn ui_entry_backspace(u: *ui) void = {
- if (len(u.entry) > 0 && u.entry_pos > 0) {
- delete(u.entry[u.entry_pos - 1]);
- u.entry_pos -= 1;
- };
-};
-
-fn ui_entry_delete(u: *ui) void = {
- if (len(u.entry) > 0 && u.entry_pos < len(u.entry)) {
- delete(u.entry[u.entry_pos]);
- };
-};
-
-fn ui_entry_right(u: *ui) void = {
- if (u.entry_pos + 1 <= len(u.entry)) {
- u.entry_pos += 1;
- };
-};
-
-fn ui_entry_right_wordwise(u: *ui) void = {
- for (u.entry_pos < len(u.entry) && u.entry[u.entry_pos] == ' ') {
- u.entry_pos += 1;
- };
- for (let i = u.entry_pos; i < len(u.entry); i += 1) {
- if (u.entry[i] == ' ') {
- u.entry_pos = i;
- return;
- };
- };
- u.entry_pos = len(u.entry);
-};
-
-fn ui_entry_left(u: *ui) void = {
- if (u.entry_pos > 0) {
- u.entry_pos -= 1;
- };
-};
-
-fn ui_entry_left_wordwise(u: *ui) void = {
- if (u.entry_pos == len(u.entry)) {
- u.entry_pos -= 1;
- };
- if (u.entry[u.entry_pos] != ' ' && u.entry[u.entry_pos - 1] == ' ') {
- u.entry_pos -= 1;
- };
- for (u.entry_pos > 0 && u.entry[u.entry_pos] == ' ') {
- u.entry_pos -= 1;
- };
- for (let i = u.entry_pos; i > 0; i -= 1) {
- if (u.entry[i] != ' ' && u.entry[i - 1] == ' ') {
- u.entry_pos = i;
- return;
- };
- };
- u.entry_pos = 0;
-};
-
-fn ui_entry_home(u: *ui) void = {
- u.entry_pos = 0;
-};
-
-fn ui_entry_end(u: *ui) void = {
- u.entry_pos = len(u.entry);
-};
-
-fn ui_entry_clear(u: *ui) void = {
- delete(u.entry[..]);
- u.entry_pos = 0;
-};
-
-fn ui_entry_kill_word(u: *ui) void = {
- if (u.entry_pos == 0) {
- return;
- };
- let seen_space = false;
- for (let i = u.entry_pos - 1; i > 0; i -= 1) {
- if (u.entry[i] == ' ') {
- seen_space = true;
- } else {
- if (seen_space) {
- delete(u.entry[i + 1..]);
- u.entry_pos = i + 1;
- return;
- };
- };
- };
- delete(u.entry[..]);
- u.entry_pos = 0;
-};
-
-fn print_rational(u: *ui, n: rational::number) (size | io::error) = {
- if (!u.options.mixed) {
- return rational::fmt(u.term, n);
- };
-
- const mixed = n.numerator / n.denominator: i64;
- n.numerator %= n.denominator: i64;
- if (mixed == 0) {
- return rational::fmt(u.term, n);
- };
- if (n.numerator == 0) {
- return fmt::fprint(u.term, mixed);
- };
- if (rational::sign(n) > 0) {
- return fmt::fprintf(u.term, "{} + {}/{}",
- mixed, n.numerator, n.denominator);
- } else {
- return fmt::fprintf(u.term, "-({} + {}/{})",
- -mixed, -n.numerator, n.denominator);
- };
-};
D vt/cmd/vttest/main.ha => vt/cmd/vttest/main.ha +0 -64
@@ 1,64 0,0 @@
-use fmt;
-use unix::tty;
-use vt;
-use io;
-
-export fn main() void = {
- const term = tty::open()!;
- defer io::close(term)!;
-
- const termios = tty::termios_query(term)!;
- defer tty::termios_restore(&termios);
-
- tty::makeraw(&termios)!;
-
- let exit = false;
- for (!exit) {
- const (key, modifiers) = match (vt::nextkey(term)) {
- case let k: vt::key =>
- yield k;
- case let err: vt::error =>
- continue;
- case io::EOF =>
- break;
- };
-
- match (key) {
- case let r: rune =>
- fmt::fprintf(term, "Key: {:x}, modifiers: [", r: u32)!;
- case let kk: vt::keycode =>
- const s = switch (kk) {
- case vt::keycode::ESC =>
- exit = true;
- yield "ESC";
- case vt::keycode::DOWN => yield "DOWN";
- case vt::keycode::UP => yield "UP";
- case vt::keycode::LEFT => yield "LEFT";
- case vt::keycode::RIGHT => yield "RIGHT";
- case vt::keycode::HOME => yield "HOME";
- case vt::keycode::END => yield "END";
- case vt::keycode::PAGE_DOWN => yield "PAGE_DOWN";
- case vt::keycode::PAGE_UP => yield "PAGE_UP";
- case vt::keycode::INSERT => yield "INSERT";
- case vt::keycode::DELETE => yield "DELETE";
- case vt::keycode::FUNCTION => yield "FUNCTION";
- case => abort();
- };
- fmt::fprintf(term, "Key: {}, modifiers: [", s)!;
- };
-
- if (modifiers & vt::modifiers::SHIFT != 0) {
- fmt::fprint(term, " SHIFT")!;
- };
- if (modifiers & vt::modifiers::ALT != 0) {
- fmt::fprint(term, " ALT")!;
- };
- if (modifiers & vt::modifiers::CONTROL != 0) {
- fmt::fprint(term, " CONTROL")!;
- };
- if (modifiers & vt::modifiers::META != 0) {
- fmt::fprint(term, " META")!;
- };
- fmt::fprint(term, " ]\n\r")!;
- };
-};
D vt/parser.ha => vt/parser.ha +0 -193
@@ 1,193 0,0 @@
-use ascii;
-use bufio;
-use io;
-
-// Special keys.
-export type keycode = enum {
- ESC,
-
- DOWN,
- UP,
- LEFT,
- RIGHT,
- HOME,
- END,
- PAGE_DOWN,
- PAGE_UP,
-
- BACKSPACE,
- DELETE,
- ENTER,
- INSERT,
-
- FUNCTION, // F1 - F20, all TODO
-};
-
-// Modifiers that are applied to a key press.
-export type modifiers = enum uint {
- NONE = 0,
- SHIFT = (1 << 0),
- ALT = (1 << 1),
- CONTROL = (1 << 2),
- META = (1 << 3),
-};
-
-// A key press event.
-export type key = ((rune | keycode), modifiers);
-
-def ESC: rune = '\u001b';
-
-// Parses an input sequence from a TTY.
-export fn nextkey(in: io::handle) (key | io::EOF | error) = {
- const r = match (bufio::read_rune(in)?) {
- case let r: rune =>
- yield r;
- case io::EOF =>
- return io::EOF;
- };
-
- if (r == ESC) {
- return parse_escape(in);
- } else if (ascii::iscntrl(r)) {
- return control_lookup(r);
- } else {
- return (r, modifiers::NONE);
- };
-};
-
-fn parse_escape(in: io::handle) (key | error) = {
- const r = wantrune(in)?;
- switch (r) {
- case '[' =>
- // <esc> '[' -> keycode sequence
- return parse_sequence(in);
- case ESC =>
- // <esc> <esc> -> esc
- return (keycode::ESC, modifiers::NONE);
- case =>
- // <esc> <char> -> Alt-keypress
- return (r, modifiers::ALT);
- };
-};
-
-fn parse_sequence(in: io::handle) (key | error) = {
- let r = wantrune(in)?;
-
- if (!ascii::isdigit(r) && !ascii::isupper(r)) {
- return invalid;
- };
-
- if (ascii::isupper(r)) {
- // <esc> '[' <char>, i.e. xterm sequence without modifiers
- return (xterm_lookup(r)?, modifiers::NONE);
- };
-
- // Either xterm with modifiers or vt
- let n = 0;
- for (ascii::isdigit(r); r = wantrune(in)?) {
- n *= 10;
- n += todigit(r);
- };
- switch (r) {
- case ';' =>
- // <esc> '[' <keycode> ';' <modifier> '~', i.e. vt with modifiers
- // or
- // <esc> '[' <> ';' <modifier> <char>, i.e. xterm with modifiers
- let mod = 0;
- let r = wantrune(in)?;
- for (ascii::isdigit(r); r = wantrune(in)?) {
- mod *= 10;
- mod += todigit(r);
- };
- if (ascii::isupper(r)) {
- return (xterm_lookup(r)?, modifier_lookup(mod)?);
- } else if (r == '~') {
- return (vt_lookup(n)?, modifier_lookup(mod)?);
- } else {
- return invalid;
- };
- case '~' =>
- // <esc> '[' <keycode> '~', i.e. vt without modifiers
- return (vt_lookup(n)?, modifiers::NONE);
- case => void;
- };
-
- // The only remaining variant is xterm with modifiers
- if (!ascii::isupper(r)) {
- return invalid;
- };
- return (xterm_lookup(r)?, modifier_lookup(n)?);
-};
-
-fn todigit(r: rune) int = (r: u32 - '0': u32): int;
-
-fn wantrune(in: io::handle) (rune | error) = {
- match (bufio::read_rune(in)?) {
- case let r: rune =>
- return r;
- case io::EOF =>
- return invalid;
- };
-};
-
-fn xterm_lookup(r: rune) (keycode | error) = {
- switch (r) {
- case 'A' => return keycode::UP;
- case 'B' => return keycode::DOWN;
- case 'C' => return keycode::RIGHT;
- case 'D' => return keycode::LEFT;
- case 'F' => return keycode::END;
- case 'G' => abort(); // I have some questions
- case 'H' => return keycode::HOME;
- case 'P', 'Q', 'R', 'S' => return keycode::FUNCTION;
- case => return invalid;
- };
-};
-
-fn vt_lookup(n: int) (keycode | error) = {
- switch (n) {
- case 1, 7 =>
- return keycode::HOME;
- case 2 =>
- return keycode::INSERT;
- case 3 =>
- return keycode::DELETE;
- case 4, 8 =>
- return keycode::END;
- case 5 =>
- return keycode::PAGE_UP;
- case 6 =>
- return keycode::PAGE_DOWN;
- case 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28,
- 29, 31, 32, 33, 34, 35 =>
- return keycode::FUNCTION;
- case => return invalid;
- };
-};
-
-fn control_lookup(r: rune) (key | error) = {
- const r = r: u32;
- switch (r) {
- case 0x08 =>
- return (keycode::DELETE, modifiers::NONE);
- case 0x7f =>
- return (keycode::BACKSPACE, modifiers::NONE);
- case '\r' =>
- return (keycode::ENTER, modifiers::NONE);
- case => void;
- };
- if (r >= 0x01 && r <= 0x1a) {
- return ((r - 1 + 'a': u32): rune, modifiers::CONTROL);
- };
- return invalid;
-};
-
-fn modifier_lookup(n: int) (modifiers | error) = {
- if (n == 0) {
- // Should we default to 1 instead?
- return invalid;
- };
- n -= 1;
- // TODO: check if other flags are set
- return n: modifiers;
-};
D vt/types.ha => vt/types.ha +0 -20
@@ 1,20 0,0 @@
-use encoding::utf8;
-use io;
-
-// Invalid input sequence.
-export type invalid = !void;
-
-// Any error that may occur.
-export type error = !(io::error | utf8::invalid | invalid);
-
-// Returns a string representation of an [[error]].
-export fn strerror(err: error) const str = {
- match (err) {
- case let err: io::error =>
- return io::strerror(err);
- case utf8::invalid =>
- return "Invalid UTF-8 input";
- case invalid =>
- return "Invalid input sequence";
- };
-};