Skip to content

Commit

Permalink
ChafaTermInfo: Implement simple seq parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
hpjansson committed Dec 10, 2022
1 parent 26945b6 commit df1fdf0
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 2 deletions.
149 changes: 147 additions & 2 deletions chafa/chafa-term-info.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,6 @@
* An enumeration of the control sequences supported by #ChafaTermInfo.
**/

/* Maximum number of arguments + 1 for the sentinel */
#define CHAFA_TERM_SEQ_ARGS_MAX 8
#define ARG_INDEX_SENTINEL 255

typedef struct
Expand Down Expand Up @@ -382,6 +380,115 @@ emit_seq_3_args_uint16_hex (const ChafaTermInfo *term_info, gchar *out, ChafaTer
return emit_seq_guint16_hex (term_info, out, seq, args, 3);
}

/* Stream parsing */

static gint
parse_dec (const gchar *in, gint in_len, guint *args_out)
{
gint i = 0;
guint result = 0;

while (in_len > 0 && *in >= '0' && *in <= '9')
{
result *= 10;
result += *in - '0';
in++;
in_len--;
i++;
}

*args_out = result;
return i;
}

static gint
parse_hex4 (const gchar *in, gint in_len, guint *args_out)
{
gint i = 0;
guint result = 0;

while (in_len > 0)
{
gchar c = g_ascii_tolower (*in);

if (c >= '0' && c <= '9')
{
result *= 16;
result += c - '0';
}
else if (c >= 'a' && c <= 'f')
{
result *= 16;
result += c - 'a' + 10;
}
else
break;

in++;
in_len--;
i++;
}

*args_out = result;
return i;
}

static ChafaParseResult
try_parse_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq,
gchar **input, gint *input_len, guint *args_out)
{
gchar *in = *input;
gint in_len = *input_len;
const gchar *seq_str;
const SeqArgInfo *seq_args;
gint pofs = 0;
guint i = 0;

seq_str = &term_info->seq_str [seq] [0];
seq_args = &term_info->seq_args [seq] [0];

memset (args_out, 0, seq_meta [seq].n_args * sizeof (guint));

for ( ; ; i++)
{
gint len;

if (memcmp (in, &seq_str [pofs], MIN (in_len, seq_args [i].pre_len)))
return CHAFA_PARSE_FAILURE;
if (in_len < seq_args [i].pre_len)
return CHAFA_PARSE_AGAIN;
in += seq_args [i].pre_len;
in_len -= seq_args [i].pre_len;
pofs += seq_args [i].pre_len;

if (i >= seq_meta [seq].n_args)
break;

if (in_len == 0)
return CHAFA_PARSE_AGAIN;

if (seq_meta [seq].type_size == 1)
len = parse_dec (in, in_len, args_out + seq_args [i].arg_index);
else if (seq_meta [seq].type_size == 2)
len = parse_hex4 (in, in_len, args_out + seq_args [i].arg_index);
else
len = parse_dec (in, in_len, args_out + seq_args [i].arg_index);

if (len == 0)
return CHAFA_PARSE_FAILURE;

in += len;
in_len -= len;
}

if (*input == in)
return CHAFA_PARSE_FAILURE;

*input = in;
*input_len = in_len;
return CHAFA_PARSE_SUCCESS;
}

/* Public */

G_DEFINE_QUARK (chafa-term-info-error-quark, chafa_term_info_error)
Expand Down Expand Up @@ -694,6 +801,44 @@ chafa_term_info_emit_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, ...)
return result;
}

/**
* chafa_term_info_emit_seq:
* @term_info: A #ChafaTermInfo
* @seq: A #ChafaTermSeq to attempt to parse
* @input: Pointer to pointer to input data
* @input_len: Pointer to maximum input data length
* @args_out: Pointer to parsed argument values
*
* Attempts to parse a terminal sequence from an input data array. If successful,
* #CHAFA_PARSE_SUCCESS will be returned, the @input pointer will be advanced and
* the parsed length will be subtracted from @input_len.
*
* Returns: A #ChafaParseResult indicating success, failure or insufficient input data
*
* Since: 1.14
**/
ChafaParseResult
chafa_term_info_parse_seq (ChafaTermInfo *term_info, ChafaTermSeq seq,
gchar **input, gint *input_len,
guint *args_out)
{
guint dummy_args_out [CHAFA_TERM_SEQ_ARGS_MAX];

g_return_val_if_fail (term_info != NULL, CHAFA_PARSE_FAILURE);
g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, CHAFA_PARSE_FAILURE);
g_return_val_if_fail (input != NULL, CHAFA_PARSE_FAILURE);
g_return_val_if_fail (*input != NULL, CHAFA_PARSE_FAILURE);
g_return_val_if_fail (input_len != NULL, CHAFA_PARSE_FAILURE);

if (!chafa_term_info_have_seq (term_info, seq))
return CHAFA_PARSE_FAILURE;

if (!args_out)
args_out = dummy_args_out;

return try_parse_seq (term_info, seq, input, input_len, args_out);
}

/**
* chafa_term_info_supplement:
* @term_info: A #ChafaTermInfo to supplement
Expand Down
23 changes: 23 additions & 0 deletions chafa/chafa-term-info.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ G_BEGIN_DECLS

#define CHAFA_TERM_SEQ_LENGTH_MAX 96

/* Maximum number of arguments + 1 for sentinel */
#define CHAFA_TERM_SEQ_ARGS_MAX 8

#ifndef __GTK_DOC_IGNORE__
/* This declares the enum for CHAFA_TERM_SEQ_*. See chafa-term-seq-def.h
* for more information, or look up the canonical documentation at
Expand Down Expand Up @@ -73,6 +76,22 @@ typedef enum
}
ChafaTermInfoError;

/**
* ChafaParseResult:
* @CHAFA_PARSE_SUCCESS: Parsed successfully
* @CHAFA_PARSE_FAILURE: Data mismatch
* @CHAFA_PARSE_AGAIN: Partial success, but not enough input
*
* An enumeration of the possible return values from the parsing function.
**/
typedef enum
{
CHAFA_PARSE_SUCCESS,
CHAFA_PARSE_FAILURE,
CHAFA_PARSE_AGAIN
}
ChafaParseResult;

CHAFA_AVAILABLE_IN_1_6
GQuark chafa_term_info_error_quark (void);

Expand All @@ -94,6 +113,10 @@ CHAFA_AVAILABLE_IN_1_6
gboolean chafa_term_info_have_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq);
CHAFA_AVAILABLE_IN_1_14
gchar *chafa_term_info_emit_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, ...);
CHAFA_AVAILABLE_IN_1_14
ChafaParseResult chafa_term_info_parse_seq (ChafaTermInfo *term_info, ChafaTermSeq seq,
gchar **input, gint *input_len,
guint *args_out);

CHAFA_AVAILABLE_IN_1_6
void chafa_term_info_supplement (ChafaTermInfo *term_info, ChafaTermInfo *source);
Expand Down
53 changes: 53 additions & 0 deletions tests/term-info-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,58 @@ dynamic_test (void)
/* Too few (zero) args */
dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, -1);
g_assert (dyn == NULL);

chafa_term_info_unref (ti);
}

static void
parsing_test (void)
{
ChafaTermInfo *ti;
gchar *dyn;
gint dyn_len;
gchar *p;
guint args [CHAFA_TERM_SEQ_ARGS_MAX];
ChafaParseResult result;
guint i;

ti = chafa_term_info_new ();

/* Define and emit */

chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, "def-fg-%1-%2-%3,", NULL);
dyn = chafa_term_info_emit_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG,
0xffff, 0x0000, 0x1234, -1);

/* Parse success */

p = dyn;
dyn_len = strlen (dyn);
result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args);
g_assert (result == CHAFA_PARSE_SUCCESS);
g_assert (args [0] == 0xffff);
g_assert (args [1] == 0x0000);
g_assert (args [2] == 0x1234);

/* Not enough data */

for (i = 0; i < strlen (dyn); i++)
{
p = dyn;
dyn_len = i;
result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args);
g_assert (result == CHAFA_PARSE_AGAIN);
}

/* Parse failure */

p = dyn + 1;
dyn_len = strlen (dyn) - 1;
result = chafa_term_info_parse_seq (ti, CHAFA_TERM_SEQ_SET_DEFAULT_FG, &p, &dyn_len, args);
g_assert (result == CHAFA_PARSE_FAILURE);

g_free (dyn);
chafa_term_info_unref (ti);
}

int
Expand All @@ -133,6 +185,7 @@ main (int argc, char *argv [])

g_test_add_func ("/term-info/formatting", formatting_test);
g_test_add_func ("/term-info/dynamic", dynamic_test);
g_test_add_func ("/term-info/parsing", parsing_test);

return g_test_run ();
}

0 comments on commit df1fdf0

Please sign in to comment.