lambda-calc-1/handle_interactive.c

441 lines
11 KiB
C

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <termios.h>
#include <unistd.h>
#include <debug.h>
#include <memory/smalloc.h>
#include <memory/srealloc.h>
#include "handle_interactive.h"
static const enum state {
s_error,
s_EOT,
s_delete,
s_backspace,
s_insert_letter,
s_up,
s_down,
s_right,
s_left,
s_home,
s_end,
s_start,
s_esc,
s_csi,
number_of_states,
} lookup[number_of_states][256] = {
[s_start][0x04] = s_EOT,
[s_start][0x7F] = s_backspace,
[s_start]['0' ... '9'] = s_insert_letter,
[s_start]['a' ... 'z'] = s_insert_letter,
[s_start]['A' ... 'Z'] = s_insert_letter,
[s_start][0x1B] = s_esc,
[s_esc]['['] = s_csi,
[s_start][0x9B] = s_csi,
[s_csi]['3'] = s_delete,
[s_csi]['A'] = s_up,
[s_csi]['B'] = s_down,
[s_csi]['C'] = s_right,
[s_csi]['D'] = s_left,
[s_csi]['H'] = s_home,
[s_csi]['F'] = s_end,
};
void handle_interactive(
struct environment** environment,
struct booleans* booleans)
{
ENTER;
struct termios termios;
if (tcgetattr(/* fd: */ 1, /* struct termios: */ &termios) < 0)
{
TODO;
exit(1);
}
struct termios original = termios;
termios.c_lflag &= ~(unsigned) ICANON; // disable ICANON.
termios.c_lflag &= ~(unsigned) ECHO; // disable ECHO.
if (tcsetattr(/* fd: */ 1, /* when?: */ TCSADRAIN, /* struct termios: */ &termios) < 0)
{
TODO;
exit(1);
}
size_t width = 50;
enum state state = s_start;
struct letter {
wchar_t code;
struct {
uint8_t r, g, b;
} fg, bg;
};
struct {
struct letter* data;
size_t pos, cap;
} live = {};
{
live.data = smalloc(sizeof(*live.data) * width);
live.cap = width;
memset(live.data, 0, sizeof(*live.data) * width);
}
struct {
struct letter* data;
size_t pos, n, cap;
} line = {};
void ensure_capacity(
size_t newcap)
{
while (line.cap < newcap)
{
line.cap = line.cap << 1 ?: 1;
line.data = srealloc(line.data, sizeof(*line.data) * line.cap);
}
}
void remove_from_line_at_pos(void)
{
assert(line.pos <= line.n);
memmove(
/* dest: */ line.data + line.pos,
/* src: */ line.data + line.pos + 1,
/* n: */ sizeof(*line.data) * (line.n - line.pos - 1));
line.n--;
}
void insert_into_line_at_pos(
wchar_t code)
{
assert(line.pos <= line.n);
ensure_capacity(line.cap + 1);
memmove(
/* dest: */ line.data + line.pos + 1,
/* src: */ line.data + line.pos,
/* n: */ sizeof(*line.data) * (line.n - line.pos));
struct letter letter = {
.code = code,
.fg = {255, 255, 255},
.bg = { 0, 0, 0},
};
line.data[line.pos] = letter;
line.n++;
}
void redraw(void)
{
size_t pos = live.pos;
assert(line.n <= width);
size_t i = 0;
for (; i < width; i++)
{
struct letter* intended_letter = i < line.n ? &line.data[i] : &(struct letter) {.code = ' '};
struct letter* live_letter = &live.data[i];
if (intended_letter->code != live_letter->code)
{
if (pos != i)
{
printf("\e[%zuG", i + 1); // Move cursor to indicated column in current row.
pos = i;
}
printf("%c", (uint8_t) intended_letter->code);
live_letter->code = intended_letter->code;
pos++;
}
}
if (pos != line.pos)
{
printf("\e[%zuG", line.pos + 1); // Move cursor to indicated column in current row.
live.pos = pos;
}
fflush(stdout);
}
struct {
uint8_t* data;
size_t n, cap;
} token = {};
void append(uint8_t byte)
{
if (token.n == token.cap)
{
token.cap = token.cap << 1 ?: 1;
token.data = srealloc(token.data, sizeof(*token.data) * token.cap);
}
token.data[token.n++] = byte;
}
void prettyprint_token(void)
{
puts("");
puts("prettyprint_token:");
for (size_t i = 0; i < token.n; i++)
{
switch (token.data[i])
{
case '\e':
printf("[%zu] = '\\e'\n", i);
break;
case '\n':
printf("[%zu] = '\\n'\n", i);
break;
default:
printf("[%zu] = '%c' (0x%02hhX)\n", i, token.data[i], token.data[i]);
break;
}
}
fflush(stdout);
}
uint8_t buffer[4096];
for (bool keep_going = true, error = false; !error && keep_going; )
{
ssize_t retval = read(0, buffer, sizeof(buffer));
if (retval < 0)
{
TODO;
}
else if (!retval)
{
TODO;
}
else for (ssize_t i = 0; i < retval; i++)
{
state = lookup[state][buffer[i]];
append(buffer[i]);
if (state < s_start)
{
switch (state)
{
case s_error:
{
prettyprint_token();
exit(1);
break;
}
case s_EOT:
{
keep_going = false;
break;
}
case s_backspace:
{
if (0 < line.pos)
{
line.pos--;
remove_from_line_at_pos();
redraw();
}
break;
}
case s_delete:
{
if (line.pos < line.n)
{
remove_from_line_at_pos();
redraw();
}
break;
}
case s_insert_letter:
{
insert_into_line_at_pos(buffer[i]), line.pos++;
redraw();
break;
}
case s_up:
break;
case s_down:
break;
case s_right:
{
if (line.pos < line.n)
{
line.pos++;
redraw();
}
break;
}
case s_left:
{
if (0 < line.pos)
{
line.pos--;
redraw();
}
break;
}
case s_home:
{
line.pos = 0;
redraw();
break;
}
case s_end:
{
line.pos = line.n;
redraw();
break;
}
default:
{
prettyprint_token();
exit(1);
break;
}
}
state = s_start;
token.n = 0;
}
}
}
// running them through a tokenizer-like state machine?
// different states have corasponding actions
if (tcsetattr(/* fd: */ 1, /* when?: */ TCSADRAIN, /* struct termios: */ &original) < 0)
{
TODO;
exit(1);
}
free(line.data);
EXIT;
}
#if 0
// maybe this should go into it's own file, because it's complicated \
beyond parsing and executing the expression.
// maintain the line as a character array
// whenever there's a mutation, re-parse
// if the text is empty:
// grey the prompt
// the input will never contain a newline, it might contain a semicolon
// color the expression \
which will color the tokens \
maybe set all tokens to grey first?
// redraw the input line with colored tokens
// the parse will come back happy or unhappy:
// if happy:
// make the prompt green
// if unhappy:
// make prompt red
// for all found 'syntax error' expressions:
// print it's message on a new line, offset at the position \
of the incedent.
// if the user hits enter:
// ... on an empty prompt:
// new line, redraw grey prompt.
// ... on an unhappy expression:
// maybe blink the error messages?
// ... on a happy expression:
// new line
// evaluate
// '${id} = ' print result
// new line
// grey the prompt.
// increment line number
// save the result into '${id++}'
// free result
#endif