]> sigrok.org Git - libsigrok.git/blobdiff - src/input/csv.c
input/csv: eliminate magic numbers in options declaration
[libsigrok.git] / src / input / csv.c
index ad18e32293cde662cedbc55994dc84ce5350b9ae..b8574d3e0542efee8233d4cd9571869075da13f6 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
+#include <glib.h>
 #include <stdlib.h>
 #include <string.h>
-#include <glib.h>
+
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "input/csv"
 
-#define DATAFEED_MAX_SAMPLES   (128 * 1024)
+#define CHUNK_SIZE     (4 * 1024 * 1024)
 
 /*
  * The CSV input module has the following options:
  */
 
 /* Single column formats. */
-enum {
+enum single_col_format {
        FORMAT_BIN,
        FORMAT_HEX,
-       FORMAT_OCT
+       FORMAT_OCT,
 };
 
 struct context {
@@ -116,7 +118,7 @@ struct context {
        uint64_t samplerate;
 
        /* Number of channels. */
-       unsigned int num_channels;
+       size_t num_channels;
 
        /* Column delimiter character(s). */
        GString *delimiter;
@@ -131,20 +133,20 @@ struct context {
        gboolean multi_column_mode;
 
        /* Column number of the sample data in single column mode. */
-       unsigned int single_column;
+       size_t single_column;
 
        /*
         * Number of the first column to parse. Equivalent to the number of the
         * first channel in multi column mode and the single column number in
         * single column mode.
         */
-       unsigned int first_column;
+       size_t first_column;
 
        /*
         * Column number of the first channel in multi column mode and position of
         * the bit for the first channel in single column mode.
         */
-       unsigned int first_channel;
+       size_t first_channel;
 
        /* Line number to start processing. */
        size_t start_line;
@@ -156,7 +158,7 @@ struct context {
        gboolean header;
 
        /* Format sample data is stored in single column mode. */
-       int format;
+       enum single_col_format format;
 
        size_t sample_unit_size;        /**!< Byte count for a single sample. */
        uint8_t *sample_buffer;         /**!< Buffer for a single sample. */
@@ -167,6 +169,9 @@ struct context {
 
        /* Current line number. */
        size_t line_number;
+
+       /* List of previously created sigrok channels. */
+       GSList *prev_sr_channels;
 };
 
 static void strip_comment(char *buf, const GString *prefix)
@@ -187,7 +192,7 @@ static int parse_binstr(const char *str, struct context *inc)
        length = strlen(str);
 
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
@@ -201,7 +206,7 @@ static int parse_binstr(const char *str, struct context *inc)
                if (str[length - i - 1] == '1') {
                        inc->sample_buffer[j / 8] |= (1 << (j % 8));
                } else if (str[length - i - 1] != '0') {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+                       sr_err("Invalid value '%s' in column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
@@ -219,7 +224,7 @@ static int parse_hexstr(const char *str, struct context *inc)
        length = strlen(str);
 
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
@@ -234,7 +239,7 @@ static int parse_hexstr(const char *str, struct context *inc)
                c = str[length - i - 1];
 
                if (!g_ascii_isxdigit(c)) {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+                       sr_err("Invalid value '%s' in column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
@@ -263,7 +268,7 @@ static int parse_octstr(const char *str, struct context *inc)
        length = strlen(str);
 
        if (!length) {
-               sr_err("Column %u in line %zu is empty.", inc->single_column,
+               sr_err("Column %zu in line %zu is empty.", inc->single_column,
                        inc->line_number);
                return SR_ERR;
        }
@@ -278,7 +283,7 @@ static int parse_octstr(const char *str, struct context *inc)
                c = str[length - i - 1];
 
                if (c < '0' || c > '7') {
-                       sr_err("Invalid value '%s' in column %u in line %zu.",
+                       sr_err("Invalid value '%s' in column %zu in line %zu.",
                                str, inc->single_column, inc->line_number);
                        return SR_ERR;
                }
@@ -298,7 +303,16 @@ static int parse_octstr(const char *str, struct context *inc)
        return SR_OK;
 }
 
-static char **parse_line(char *buf, struct context *inc, int max_columns)
+/**
+ * @brief Splits a text line into a set of columns.
+ *
+ * @param[in] buf      The input text line to split.
+ * @param[in] inc      The input module's context.
+ * @param[in] max_cols The maximum column count, negative to get all of them.
+ *
+ * @returns An array of strings, representing the columns' text.
+ */
+static char **parse_line(char *buf, struct context *inc, ssize_t max_cols)
 {
        const char *str, *remainder;
        GSList *list, *l;
@@ -313,12 +327,12 @@ static char **parse_line(char *buf, struct context *inc, int max_columns)
        remainder = buf;
        str = strstr(remainder, inc->delimiter->str);
 
-       while (str && max_columns) {
+       while (str && max_cols) {
                if (n >= inc->first_column) {
                        column = g_strndup(remainder, str - remainder);
                        list = g_slist_prepend(list, g_strstrip(column));
 
-                       max_columns--;
+                       max_cols--;
                        k++;
                }
 
@@ -327,7 +341,7 @@ static char **parse_line(char *buf, struct context *inc, int max_columns)
                n++;
        }
 
-       if (buf[0] && max_columns && n >= inc->first_column) {
+       if (buf[0] && max_cols && n >= inc->first_column) {
                column = g_strdup(remainder);
                list = g_slist_prepend(list, g_strstrip(column));
                k++;
@@ -446,10 +460,10 @@ static int init(struct sr_input *in, GHashTable *options)
        in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
        in->priv = inc = g_malloc0(sizeof(struct context));
 
-       inc->single_column = g_variant_get_int32(g_hash_table_lookup(options, "single-column"));
+       inc->single_column = g_variant_get_uint32(g_hash_table_lookup(options, "single-column"));
        inc->multi_column_mode = inc->single_column == 0;
 
-       inc->num_channels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels"));
+       inc->num_channels = g_variant_get_uint32(g_hash_table_lookup(options, "numchannels"));
 
        inc->delimiter = g_string_new(g_variant_get_string(
                        g_hash_table_lookup(options, "delimiter"), NULL));
@@ -481,11 +495,11 @@ static int init(struct sr_input *in, GHashTable *options)
 
        inc->samplerate = g_variant_get_uint64(g_hash_table_lookup(options, "samplerate"));
 
-       inc->first_channel = g_variant_get_int32(g_hash_table_lookup(options, "first-channel"));
+       inc->first_channel = g_variant_get_uint32(g_hash_table_lookup(options, "first-channel"));
 
        inc->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
 
-       inc->start_line = g_variant_get_int32(g_hash_table_lookup(options, "startline"));
+       inc->start_line = g_variant_get_uint32(g_hash_table_lookup(options, "startline"));
        if (inc->start_line < 1) {
                sr_err("Invalid start line %zu.", inc->start_line);
                return SR_ERR_ARG;
@@ -504,6 +518,44 @@ static int init(struct sr_input *in, GHashTable *options)
        return SR_OK;
 }
 
+/*
+ * Check the channel list for consistency across file re-import. See
+ * the VCD input module for more details and motivation.
+ */
+
+static void keep_header_for_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       inc = in->priv;
+       g_slist_free_full(inc->prev_sr_channels, sr_channel_free_cb);
+       inc->prev_sr_channels = in->sdi->channels;
+       in->sdi->channels = NULL;
+}
+
+static int check_header_in_reread(const struct sr_input *in)
+{
+       struct context *inc;
+
+       if (!in)
+               return FALSE;
+       inc = in->priv;
+       if (!inc)
+               return FALSE;
+       if (!inc->prev_sr_channels)
+               return TRUE;
+
+       if (sr_channel_lists_differ(inc->prev_sr_channels, in->sdi->channels)) {
+               sr_err("Channel list change not supported for file re-read.");
+               return FALSE;
+       }
+       g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+       in->sdi->channels = inc->prev_sr_channels;
+       inc->prev_sr_channels = NULL;
+
+       return TRUE;
+}
+
 static const char *delim_set = "\r\n";
 
 static const char *get_line_termination(GString *buf)
@@ -525,7 +577,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
 {
        struct context *inc;
        GString *channel_name;
-       unsigned int num_columns, i;
+       size_t num_columns, i;
        size_t line_number, l;
        int ret;
        char **lines, *line, **columns, *column;
@@ -576,7 +628,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
 
        /* Ensure that the first column is not out of bounds. */
        if (!num_columns) {
-               sr_err("Column %u in line %zu is out of bounds.",
+               sr_err("Column %zu in line %zu is out of bounds.",
                        inc->first_column, line_number);
                ret = SR_ERR;
                goto out;
@@ -589,7 +641,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
                 */
                if (!inc->num_channels) {
                        inc->num_channels = num_columns;
-                       sr_dbg("Number of auto-detected channels: %u.",
+                       sr_dbg("Number of auto-detected channels: %zu.",
                                inc->num_channels);
                }
 
@@ -611,10 +663,14 @@ static int initial_parse(const struct sr_input *in, GString *buf)
                if (inc->header && inc->multi_column_mode && column[0] != '\0')
                        g_string_assign(channel_name, column);
                else
-                       g_string_printf(channel_name, "%u", i);
+                       g_string_printf(channel_name, "%zu", i);
                sr_channel_new(in->sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name->str);
        }
        g_string_free(channel_name, TRUE);
+       if (!check_header_in_reread(in)) {
+               ret = SR_ERR_DATA;
+               goto out;
+       }
 
        /*
         * Calculate the minimum buffer size to store the set of samples
@@ -624,7 +680,7 @@ static int initial_parse(const struct sr_input *in, GString *buf)
         * to a location within that large buffer.
         */
        inc->sample_unit_size = (inc->num_channels + 7) / 8;
-       inc->datafeed_buf_size = DATAFEED_MAX_SAMPLES;
+       inc->datafeed_buf_size = CHUNK_SIZE;
        inc->datafeed_buf_size *= inc->sample_unit_size;
        inc->datafeed_buffer = g_malloc(inc->datafeed_buf_size);
        inc->datafeed_buf_fill = 0;
@@ -704,7 +760,8 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
        struct context *inc;
        gsize num_columns;
        uint64_t samplerate;
-       int max_columns, ret, l;
+       size_t max_columns, l;
+       int ret;
        char *p, **lines, *line, **columns;
 
        inc = in->priv;
@@ -784,13 +841,15 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                columns = parse_line(line, inc, max_columns);
                if (!columns) {
                        sr_err("Error while parsing line %zu.", inc->line_number);
+                       g_strfreev(lines);
                        return SR_ERR;
                }
                num_columns = g_strv_length(columns);
                if (!num_columns) {
-                       sr_err("Column %u in line %zu is out of bounds.",
+                       sr_err("Column %zu in line %zu is out of bounds.",
                                inc->first_column, inc->line_number);
                        g_strfreev(columns);
+                       g_strfreev(lines);
                        return SR_ERR;
                }
                /*
@@ -801,6 +860,7 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                        sr_err("Not enough columns for desired number of channels in line %zu.",
                                inc->line_number);
                        g_strfreev(columns);
+                       g_strfreev(lines);
                        return SR_ERR;
                }
 
@@ -810,6 +870,7 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                        ret = parse_single_column(columns[0], inc);
                if (ret != SR_OK) {
                        g_strfreev(columns);
+                       g_strfreev(lines);
                        return SR_ERR;
                }
 
@@ -818,6 +879,7 @@ static int process_buffer(struct sr_input *in, gboolean is_eof)
                if (ret != SR_OK) {
                        sr_err("Sending samples failed.");
                        g_strfreev(columns);
+                       g_strfreev(lines);
                        return SR_ERR;
                }
 
@@ -882,16 +944,14 @@ static void cleanup(struct sr_input *in)
 {
        struct context *inc;
 
-       inc = in->priv;
-
-       if (inc->delimiter)
-               g_string_free(inc->delimiter, TRUE);
+       keep_header_for_reread(in);
 
-       if (inc->comment)
-               g_string_free(inc->comment, TRUE);
+       inc = in->priv;
 
        g_free(inc->termination);
+       inc->termination = NULL;
        g_free(inc->datafeed_buffer);
+       inc->datafeed_buffer = NULL;
 }
 
 static int reset(struct sr_input *in)
@@ -905,31 +965,51 @@ static int reset(struct sr_input *in)
        return SR_OK;
 }
 
+enum option_index {
+       OPT_SINGLE_COL,
+       OPT_NUM_LOGIC,
+       OPT_DELIM,
+       OPT_FORMAT,
+       OPT_COMMENT,
+       OPT_RATE,
+       OPT_FIRST_LOGIC,
+       OPT_HEADER,
+       OPT_START,
+       OPT_MAX,
+};
+
 static struct sr_option options[] = {
-       { "single-column", "Single column", "Enable/specify single column", NULL, NULL },
-       { "numchannels", "Max channels", "Number of channels", NULL, NULL },
-       { "delimiter", "Delimiter", "Column delimiter", NULL, NULL },
-       { "format", "Format", "Numeric format", NULL, NULL },
-       { "comment", "Comment", "Comment prefix character", NULL, NULL },
-       { "samplerate", "Samplerate", "Samplerate used during capture", NULL, NULL },
-       { "first-channel", "First channel", "Column number of first channel", NULL, NULL },
-       { "header", "Header", "Treat first line as header with channel names", NULL, NULL },
-       { "startline", "Start line", "Line number at which to start processing samples", NULL, NULL },
-       ALL_ZERO
+       [OPT_SINGLE_COL] = { "single-column", "Single column", "Enable single-column mode, using the specified column (>= 1); 0: multi-col. mode", NULL, NULL },
+       [OPT_NUM_LOGIC] = { "numchannels", "Number of logic channels", "The number of (logic) channels (single-col. mode: number of bits beginning at 'first channel', LSB-first)", NULL, NULL },
+       [OPT_DELIM] = { "delimiter", "Column delimiter", "The column delimiter (>= 1 characters)", NULL, NULL },
+       [OPT_FORMAT] = { "format", "Data format (single-col. mode)", "The numeric format of the data (single-col. mode): bin, hex, oct", NULL, NULL },
+       [OPT_COMMENT] = { "comment", "Comment character(s)", "The comment prefix character(s)", NULL, NULL },
+       [OPT_RATE] = { "samplerate", "Samplerate (Hz)", "The sample rate (used during capture) in Hz", NULL, NULL },
+       [OPT_FIRST_LOGIC] = { "first-channel", "First channel", "The column number of the first channel (multi-col. mode); bit position for the first channel (single-col. mode)", NULL, NULL },
+       [OPT_HEADER] = { "header", "Interpret first line as header (multi-col. mode)", "Treat the first line as header with channel names (multi-col. mode)", NULL, NULL },
+       [OPT_START] = { "startline", "Start line", "The line number at which to start processing samples (>= 1)", NULL, NULL },
+       [OPT_MAX] = ALL_ZERO,
 };
 
 static const struct sr_option *get_options(void)
 {
+       GSList *l;
+
        if (!options[0].def) {
-               options[0].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[1].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[2].def = g_variant_ref_sink(g_variant_new_string(","));
-               options[3].def = g_variant_ref_sink(g_variant_new_string("bin"));
-               options[4].def = g_variant_ref_sink(g_variant_new_string(";"));
-               options[5].def = g_variant_ref_sink(g_variant_new_uint64(0));
-               options[6].def = g_variant_ref_sink(g_variant_new_int32(0));
-               options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
-               options[8].def = g_variant_ref_sink(g_variant_new_int32(1));
+               options[OPT_SINGLE_COL].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_NUM_LOGIC].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_DELIM].def = g_variant_ref_sink(g_variant_new_string(","));
+               options[OPT_FORMAT].def = g_variant_ref_sink(g_variant_new_string("bin"));
+               l = NULL;
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("bin")));
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("hex")));
+               l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("oct")));
+               options[OPT_FORMAT].values = l;
+               options[OPT_COMMENT].def = g_variant_ref_sink(g_variant_new_string(";"));
+               options[OPT_RATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
+               options[OPT_FIRST_LOGIC].def = g_variant_ref_sink(g_variant_new_int32(0));
+               options[OPT_HEADER].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[OPT_START].def = g_variant_ref_sink(g_variant_new_int32(1));
        }
 
        return options;