Skip to content

Commit

Permalink
extract out automaton code #2183
Browse files Browse the repository at this point in the history
  • Loading branch information
dankamongmen committed Sep 23, 2021
1 parent 58dfa47 commit afcfaeb
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 97 deletions.
94 changes: 94 additions & 0 deletions src/lib/automaton.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "internal.h"

// we assumed escapes can only be composed of 7-bit chars
typedef struct esctrie {
struct esctrie** trie; // if non-NULL, next level of radix-128 trie
ncinput ni; // composed key terminating here
} esctrie;

uint32_t esctrie_id(const esctrie* e){
return e->ni.id;
}

void esctrie_ni(const esctrie* e, ncinput* ni){
memcpy(ni, &e->ni, sizeof(*ni));
}

esctrie** esctrie_trie(esctrie* e){
return e->trie;
}

esctrie* create_esctrie_node(int special){
esctrie* e = malloc(sizeof(*e));
if(e){
memset(e, 0, sizeof(*e));
e->ni.id = special;
}
return e;
}

void input_free_esctrie(esctrie** eptr){
esctrie* e;
if( (e = *eptr) ){
if(e->trie){
int z;
for(z = 0 ; z < 0x80 ; ++z){
if(e->trie[z]){
input_free_esctrie(&e->trie[z]);
}
}
free(e->trie);
}
free(e);
}
}

// multiple input escapes might map to the same input
int inputctx_add_input_escape(esctrie** eptr, const char* esc, uint32_t special,
unsigned shift, unsigned ctrl, unsigned alt){
if(esc[0] != NCKEY_ESC || strlen(esc) < 2){ // assume ESC prefix + content
logerror("not an escape (0x%x)\n", special);
return -1;
}
if(*eptr == NULL){
if((*eptr = create_esctrie_node(NCKEY_INVALID)) == NULL){
return -1;
}
}
esctrie* cur = *eptr;
++esc; // don't encode initial escape as a transition
do{
int valid = *esc;
if(valid <= 0 || valid >= 0x80 || valid == NCKEY_ESC){
logerror("invalid character %d in escape\n", valid);
return -1;
}
if(cur->trie == NULL){
const size_t tsize = sizeof(cur->trie) * 0x80;
if((cur->trie = malloc(tsize)) == NULL){
return -1;
}
memset(cur->trie, 0, tsize);
}
if(cur->trie[valid] == NULL){
if((cur->trie[valid] = create_esctrie_node(NCKEY_INVALID)) == NULL){
return -1;
}
}
cur = cur->trie[valid];
++esc;
}while(*esc);
// it appears that multiple keys can be mapped to the same escape string. as
// an example, see "kend" and "kc1" in st ("simple term" from suckless) :/.
if(cur->ni.id != NCKEY_INVALID){ // already had one here!
if(cur->ni.id != special){
logwarn("already added escape (got 0x%x, wanted 0x%x)\n", cur->ni.id, special);
}
}else{
cur->ni.id = special;
cur->ni.shift = shift;
cur->ni.ctrl = ctrl;
cur->ni.alt = alt;
}
return 0;
}
30 changes: 30 additions & 0 deletions src/lib/automaton.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef NOTCURSES_AUTOMATON
#define NOTCURSES_AUTOMATON

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

struct ncinput;
struct esctrie;

struct esctrie* create_esctrie_node(int special);

void input_free_esctrie(struct esctrie** eptr);

int inputctx_add_input_escape(struct esctrie** eptr, const char* esc,
uint32_t special, unsigned shift,
unsigned ctrl, unsigned alt);

uint32_t esctrie_id(const struct esctrie* e);
void esctrie_ni(const struct esctrie* e, struct ncinput* ni);
// returns 128-way array of esctrie pointers
struct esctrie** esctrie_trie(struct esctrie* e);

#ifdef __cplusplus
}
#endif

#endif
114 changes: 17 additions & 97 deletions src/lib/in.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <stdio.h>
#include "automaton.h"
#include "internal.h"
#include "in.h"

Expand Down Expand Up @@ -95,22 +96,6 @@ typedef enum {
STATE_MOUSE3, // got first mouse coordinate
} initstates_e;

// we assumed escapes can only be composed of 7-bit chars
typedef struct esctrie {
struct esctrie** trie; // if non-NULL, next level of radix-128 trie
ncinput ni; // composed key terminating here
} esctrie;

static esctrie*
create_esctrie_node(int special){
esctrie* e = malloc(sizeof(*e));
if(e){
memset(e, 0, sizeof(*e));
e->ni.id = special;
}
return e;
}

// local state for the input thread. don't put this large struct on the stack.
typedef struct inputctx {
int stdinfd; // bulk in fd. always >= 0 (almost always 0). we do not
Expand Down Expand Up @@ -184,74 +169,6 @@ inc_input_errors(inputctx* ictx){
pthread_mutex_unlock(&ictx->stats->lock);
}

static void
input_free_esctrie(esctrie** eptr){
esctrie* e;
if( (e = *eptr) ){
if(e->trie){
int z;
for(z = 0 ; z < 0x80 ; ++z){
if(e->trie[z]){
input_free_esctrie(&e->trie[z]);
}
}
free(e->trie);
}
free(e);
}
}

// multiple input escapes might map to the same input
static int
inputctx_add_input_escape(inputctx* ictx, const char* esc, uint32_t special,
unsigned shift, unsigned ctrl, unsigned alt){
if(esc[0] != NCKEY_ESC || strlen(esc) < 2){ // assume ESC prefix + content
logerror("not an escape (0x%x)\n", special);
return -1;
}
if(ictx->inputescapes == NULL){
if((ictx->inputescapes = create_esctrie_node(NCKEY_INVALID)) == NULL){
return -1;
}
}
esctrie* cur = ictx->inputescapes;
++esc; // don't encode initial escape as a transition
do{
int valid = *esc;
if(valid <= 0 || valid >= 0x80 || valid == NCKEY_ESC){
logerror("invalid character %d in escape\n", valid);
return -1;
}
if(cur->trie == NULL){
const size_t tsize = sizeof(cur->trie) * 0x80;
if((cur->trie = malloc(tsize)) == NULL){
return -1;
}
memset(cur->trie, 0, tsize);
}
if(cur->trie[valid] == NULL){
if((cur->trie[valid] = create_esctrie_node(NCKEY_INVALID)) == NULL){
return -1;
}
}
cur = cur->trie[valid];
++esc;
}while(*esc);
// it appears that multiple keys can be mapped to the same escape string. as
// an example, see "kend" and "kc1" in st ("simple term" from suckless) :/.
if(cur->ni.id != NCKEY_INVALID){ // already had one here!
if(cur->ni.id != special){
logwarn("already added escape (got 0x%x, wanted 0x%x)\n", cur->ni.id, special);
}
}else{
cur->ni.id = special;
cur->ni.shift = shift;
cur->ni.ctrl = ctrl;
cur->ni.alt = alt;
}
return 0;
}

// load all known special keys from terminfo, and build the input sequence trie
static int
prep_special_keys(inputctx* ictx){
Expand Down Expand Up @@ -420,7 +337,7 @@ prep_special_keys(inputctx* ictx){
logwarn("invalid escape: %s (0x%x)\n", k->tinfo, k->key);
continue;
}
if(inputctx_add_input_escape(ictx, seq, k->key, k->shift, k->ctrl, k->alt)){
if(inputctx_add_input_escape(&ictx->inputescapes, seq, k->key, k->shift, k->ctrl, k->alt)){
return -1;
}
logdebug("support for terminfo's %s: %s\n", k->tinfo, seq);
Expand Down Expand Up @@ -526,7 +443,7 @@ free_inputctx(inputctx* i){

// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
static int
prep_kitty_special_keys(inputctx* nc){
prep_kitty_special_keys(inputctx* ictx){
// we do not list here those already handled by prep_windows_special_keys()
static const struct {
const char* esc;
Expand All @@ -543,7 +460,8 @@ prep_kitty_special_keys(inputctx* nc){
{ .esc = NULL, .key = NCKEY_INVALID, },
}, *k;
for(k = keys ; k->esc ; ++k){
if(inputctx_add_input_escape(nc, k->esc, k->key, k->shift, k->ctrl, k->alt)){
if(inputctx_add_input_escape(&ictx->inputescapes, k->esc, k->key,
k->shift, k->ctrl, k->alt)){
return -1;
}
}
Expand All @@ -553,7 +471,7 @@ prep_kitty_special_keys(inputctx* nc){
// add the hardcoded windows input sequences to ti->input. should only
// be called after verifying that this is TERMINAL_MSTERMINAL.
static int
prep_windows_special_keys(inputctx* nc){
prep_windows_special_keys(inputctx* ictx){
// here, lacking terminfo, we hardcode the sequences. they can be found at
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
// under the "Input Sequences" heading.
Expand Down Expand Up @@ -591,7 +509,8 @@ prep_windows_special_keys(inputctx* nc){
{ .esc = NULL, .key = NCKEY_INVALID, },
}, *k;
for(k = keys ; k->esc ; ++k){
if(inputctx_add_input_escape(nc, k->esc, k->key, k->shift, k->ctrl, k->alt)){
if(inputctx_add_input_escape(&ictx->inputescapes, k->esc, k->key,
k->shift, k->ctrl, k->alt)){
return -1;
}
}
Expand Down Expand Up @@ -910,7 +829,7 @@ alt_key(inputctx* ictx, unsigned id){
pthread_mutex_lock(&ictx->ilock);
if(ictx->ivalid == ictx->isize){
pthread_mutex_unlock(&ictx->ilock);
logerror("dropping input 0x%08xx\n", ictx->triepos->ni.id);
logerror("dropping input 0x%08xx\n", esctrie_id(ictx->triepos));
inc_input_errors(ictx);
return;
}
Expand All @@ -935,12 +854,12 @@ special_key(inputctx* ictx){
pthread_mutex_lock(&ictx->ilock);
if(ictx->ivalid == ictx->isize){
pthread_mutex_unlock(&ictx->ilock);
logerror("dropping input 0x%08xx\n", ictx->triepos->ni.id);
logerror("dropping input 0x%08xx\n", esctrie_id(ictx->triepos));
inc_input_errors(ictx);
return;
}
ncinput* ni = ictx->inputs + ictx->iwrite;
memcpy(ni, &ictx->triepos->ni, sizeof(*ni));
esctrie_ni(ictx->triepos, ni);
if(++ictx->iwrite == ictx->isize){
ictx->iwrite = 0;
}
Expand Down Expand Up @@ -1593,7 +1512,7 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){
// we can safely check trie[candidate] above because we are either coming
// off the initial node, which definitely has a valid ->trie, or we're
// coming from a transition, where ictx->triepos->trie is checked below.
}else if(ictx->triepos->trie[candidate] == NULL){
}else if(esctrie_trie(ictx->triepos)[candidate] == NULL){
ictx->triepos = ictx->inputescapes;
if(ictx->state == STATE_ESC){
if(candidate && candidate <= 0x80){ // FIXME what about supraASCII utf8?
Expand All @@ -1607,13 +1526,14 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){
return -(used - 1);
}
}else{
ictx->triepos = ictx->triepos->trie[candidate];
logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos, candidate, ictx->triepos->ni.id);
if(ictx->triepos->ni.id != NCKEY_INVALID){ // match! mark and reset
ictx->triepos = esctrie_trie(ictx->triepos)[candidate];
logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos,
candidate, esctrie_id(ictx->triepos));
if(esctrie_id(ictx->triepos) != NCKEY_INVALID){ // match! mark and reset
special_key(ictx);
ictx->triepos = ictx->inputescapes;
return used;
}else if(ictx->triepos->trie == NULL){
}else if(esctrie_trie(ictx->triepos) == NULL){
if(ictx->state == STATE_NULL){
ictx->triepos = ictx->inputescapes;
// all inspected characters are invalid; return full negative "used"
Expand Down

0 comments on commit afcfaeb

Please sign in to comment.