Skip to content

Commit

Permalink
Minimal PoC for vt
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborcsardi committed Sep 5, 2022
1 parent 0539588 commit c6bea91
Show file tree
Hide file tree
Showing 9 changed files with 2,770 additions and 0 deletions.
10 changes: 10 additions & 0 deletions LICENSE.note
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

## utf8lite

Some parts of cli are based on the utf8lite library
(https://github.com/patperry/utf8lite), which is licensed
under the Apache License 2.0.
Expand All @@ -8,3 +11,10 @@ In particular,
* `src/cli.h` contains parts modified from utf8lite,
* The `src/graphbreak.h` and `src/charwidth` files are generated
with scripts from utf8lite.

## vtparse

The files `src/vtparse.h`, `src/vtparse.c`, `src/vtparse_table.h`,
`src/vtparse_tablre.c` are in the public domain, see
https://github.com/haberman/vtparse/
Pull request #1 is also included.
15 changes: 15 additions & 0 deletions R/vt.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

vt_simulate <- function(output, width = 80L, height = 25L) {
if (is.character(output)) {
output <- charToRaw(paste(output, collapse = ""))
}

screen <- .Call(
clic_vt_simulate,
output,
as.integer(width),
as.integer(height)
)

vapply(screen, intToUtf8, character(1))
}
2 changes: 2 additions & 0 deletions src/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,6 @@ void clic_utf8_graphscan_next(struct grapheme_iterator *iter,
SEXP glue_(SEXP x, SEXP f, SEXP open_arg, SEXP close_arg);
SEXP trim_(SEXP x);

SEXP clic_vt_simulate(SEXP bytes, SEXP width, SEXP height);

#endif
2 changes: 2 additions & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ static const R_CallMethodDef callMethods[] = {
{ "glue_", (DL_FUNC) glue_, 5 },
{ "trim_", (DL_FUNC) trim_, 1 },

{ "clic_vt_simulate", (DL_FUNC) clic_vt_simulate, 3 },

{ NULL, NULL, 0 }
};

Expand Down
121 changes: 121 additions & 0 deletions src/vt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@

#include <string.h>

#include "errors.h"
#include "vtparse.h"

#define CUR(term) (((term)->cursor_y) * ((term)->width) + ((term)->cursor_x))
#define POS(term, x, y) ((y) * ((term)->width) + (x))

struct terminal {
vtparse_t *vt;
int width;
int height;
unsigned int *screen;
int cursor_x;
int cursor_y;
};

void cli_term_clear_screen(struct terminal *term) {
int i, n = term->width * term->height;
for (i = 0; i < n; i++) {
term->screen[i] = ' ';
}
}

int cli_term_init(struct terminal *term, int width, int height) {
term->width = width;
term->height = height;
term->screen = (unsigned int *) R_alloc(width * height, sizeof(unsigned int));
cli_term_clear_screen(term);
return 0;
}

SEXP cli_term_lines(struct terminal *term) {
SEXP res = PROTECT(Rf_allocVector(VECSXP, term->height));
int i;

for (i = 0; i < term->height; i++) {
SEXP line = PROTECT(Rf_allocVector(INTSXP, term->width));
memcpy(
INTEGER(line),
term->screen + POS(term, 0, i),
sizeof(unsigned int) * term->width
);
SET_VECTOR_ELT(res, i, line);
UNPROTECT(1);
}

UNPROTECT(1);
return res;
}

void cli_term_move_cursor_down(struct terminal *term) {
// scroll?
if (term->cursor_y == term->height - 1) {
// TODO
}
term->cursor_y += 1;
term->cursor_x = 0;
}

void cli_term_execute(struct terminal *term, int ch) {
// TODO: rest
switch (ch) {

case '\n':
cli_term_move_cursor_down(term);
break;

case '\r':
term->cursor_x = 0;
break;

default:
break;
}
}

void clic_vt_callback(vtparse_t *vt, vtparse_action_t action,
unsigned int ch) {

struct terminal *term = (struct terminal*) vt->user_data;

switch (action) {
case VTPARSE_ACTION_EXECUTE:
cli_term_execute(term, ch);
break;

case VTPARSE_ACTION_PRINT:
term->screen[CUR(term)] = ch;
term->cursor_x += 1;
if (term->cursor_x == term->width) {
term->cursor_x = 0;
term->cursor_y += 1;
// TODO: scroll if needed. Or should we scrool at the next character
// only, so we can write at the bottom right corner?
}
break;

default:
break;
}
}

SEXP clic_vt_simulate(SEXP bytes, SEXP width, SEXP height) {
int c_width = INTEGER(width)[0];
int c_height = INTEGER(height)[0];

vtparse_t vt;
struct terminal term = { 0 };
if (cli_term_init(&term, c_width, c_height)) {
R_THROW_ERROR("Cannot initialize vittual terminal");
}
term.vt = &vt;

vtparse_init(&vt, clic_vt_callback);
vt.user_data = &term;
vtparse(&vt, RAW(bytes), LENGTH(bytes));

return cli_term_lines(&term);
}
187 changes: 187 additions & 0 deletions src/vtparse.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* VTParse - an implementation of Paul Williams' DEC compatible state machine parser
*
* Author: Joshua Haberman <[email protected]>
*
* This code is in the public domain.
*/

#include "vtparse.h"

void vtparse_init(vtparse_t *parser, vtparse_callback_t cb)
{
parser->state = VTPARSE_STATE_GROUND;
parser->num_intermediate_chars = 0;
parser->num_params = 0;
parser->ignore_flagged = 0;
parser->cb = cb;
parser->characterBytes = 1;
parser->utf8Character = 0;
}

static void do_action(vtparse_t *parser, vtparse_action_t action, unsigned int ch)
{
/* Some actions we handle internally (like parsing parameters), others
* we hand to our client for processing */

switch(action) {
case VTPARSE_ACTION_PRINT:
case VTPARSE_ACTION_EXECUTE:
case VTPARSE_ACTION_HOOK:
case VTPARSE_ACTION_PUT:
case VTPARSE_ACTION_OSC_START:
case VTPARSE_ACTION_OSC_PUT:
case VTPARSE_ACTION_OSC_END:
case VTPARSE_ACTION_UNHOOK:
case VTPARSE_ACTION_CSI_DISPATCH:
case VTPARSE_ACTION_ESC_DISPATCH:
parser->cb(parser, action, ch);
break;

case VTPARSE_ACTION_IGNORE:
/* do nothing */
break;

case VTPARSE_ACTION_COLLECT:
{
/* Append the character to the intermediate params */
if(parser->num_intermediate_chars + 1 > MAX_INTERMEDIATE_CHARS)
parser->ignore_flagged = 1;
else
parser->intermediate_chars[parser->num_intermediate_chars++] = (unsigned char)ch;

break;
}

case VTPARSE_ACTION_PARAM:
{
/* process the param character */
if(ch == ';')
{
parser->num_params += 1;
parser->params[parser->num_params-1] = 0;
}
else
{
/* the character is a digit */
int current_param;

if(parser->num_params == 0)
{
parser->num_params = 1;
parser->params[0] = 0;
}

current_param = parser->num_params - 1;
parser->params[current_param] *= 10;
parser->params[current_param] += ch - '0';
}

break;
}

case VTPARSE_ACTION_CLEAR:
parser->num_intermediate_chars = 0;
parser->num_params = 0;
parser->ignore_flagged = 0;
break;

default:
parser->cb(parser, VTPARSE_ACTION_ERROR, 0);
}
}

static void do_state_change(vtparse_t *parser, state_change_t change, unsigned int ch)
{
/* A state change is an action and/or a new state to transition to. */

vtparse_state_t new_state = STATE(change);
vtparse_action_t action = ACTION(change);


if(new_state)
{
/* Perform up to three actions:
* 1. the exit action of the old state
* 2. the action associated with the transition
* 3. the entry action of the new state
*/

vtparse_action_t exit_action = EXIT_ACTIONS[parser->state-1];
vtparse_action_t entry_action = ENTRY_ACTIONS[new_state-1];

if(exit_action)
do_action(parser, exit_action, 0);

if(action)
do_action(parser, action, ch);

if(entry_action)
do_action(parser, entry_action, 0);

parser->state = new_state;
}
else
{
do_action(parser, action, ch);
}
}

void vtparse(vtparse_t *parser, unsigned char *data, unsigned int len)
{
int i;
for(i = 0; i < len; i++)
{
unsigned char ch = data[i];
if(parser->characterBytes != 1)
{
parser->utf8Character = (parser->utf8Character << 6) | (ch & 0x3F);
parser->characterBytes--;

if(parser->characterBytes == 1)
{
state_change_t change = VTPARSE_ACTION_PRINT;
do_state_change(parser, change, parser->utf8Character);
}
}
else if((ch&(1<<7)) != 0)
{
int bit = 6;
do
{
if((ch&(1<<bit)) == 0)
{
break;
}
bit--;
}while(bit > 1);

parser->utf8Character = 0;
parser->characterBytes = 7-bit;
switch(parser->characterBytes)
{
case 2:
parser->utf8Character = ch & (1 | (1<<1) | (1<<2) | (1<<3) | (1<<4));
break;
case 3:
parser->utf8Character = ch & (1 | (1<<1) | (1<<2) | (1<<3));
break;
case 4:
parser->utf8Character = ch & (1 | (1<<1) | (1<<2));
break;
case 5:
parser->utf8Character = ch & (1 | (1<<1));
break;
case 6:
parser->utf8Character = ch & 1;
break;
}
}
else
{
state_change_t change = STATE_TABLE[parser->state-1][ch];
do_state_change(parser, change, (unsigned int)ch);
}
}
}

Loading

0 comments on commit c6bea91

Please sign in to comment.