From df1fdf0810974ab25921c14067458b622aa4911a Mon Sep 17 00:00:00 2001 From: Hans Petter Jansson Date: Sat, 10 Dec 2022 00:02:42 -0600 Subject: [PATCH] ChafaTermInfo: Implement simple seq parsing --- chafa/chafa-term-info.c | 149 +++++++++++++++++++++++++++++++++++++++- chafa/chafa-term-info.h | 23 +++++++ tests/term-info-test.c | 53 ++++++++++++++ 3 files changed, 223 insertions(+), 2 deletions(-) diff --git a/chafa/chafa-term-info.c b/chafa/chafa-term-info.c index 5293605c..5ebe1063 100644 --- a/chafa/chafa-term-info.c +++ b/chafa/chafa-term-info.c @@ -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 @@ -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) @@ -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 diff --git a/chafa/chafa-term-info.h b/chafa/chafa-term-info.h index d309e288..b437cead 100644 --- a/chafa/chafa-term-info.h +++ b/chafa/chafa-term-info.h @@ -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 @@ -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); @@ -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); diff --git a/tests/term-info-test.c b/tests/term-info-test.c index 3a444eb5..1cfb2054 100644 --- a/tests/term-info-test.c +++ b/tests/term-info-test.c @@ -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 @@ -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 (); }