+static char **split_text_line(struct context *inc, char *text, size_t *count)
+{
+ struct split_state *state;
+ size_t counted, alloced, wanted;
+ char **words, *p, **new_words;
+
+ state = &inc->split;
+
+ if (count)
+ *count = 0;
+
+ if (state->in_use) {
+ sr_dbg("coding error, split() called while \"in use\".");
+ return NULL;
+ }
+
+ /*
+ * Seed allocation when invoked for the first time. Assume
+ * simple logic data, start with a few words per line. Will
+ * automatically adjust with subsequent use.
+ */
+ if (!state->alloced) {
+ alloced = 20;
+ words = g_malloc(sizeof(words[0]) * alloced);
+ if (!words)
+ return NULL;
+ state->alloced = alloced;
+ state->words = words;
+ }
+
+ /* Start with most recently allocated word list space. */
+ alloced = state->alloced;
+ words = state->words;
+ counted = 0;
+
+ /* As long as more input text remains ... */
+ p = text;
+ while (*p) {
+ /* Resize word list if needed. Just double the size. */
+ if (counted + 1 >= alloced) {
+ wanted = 2 * alloced;
+ new_words = g_realloc(words, sizeof(words[0]) * wanted);
+ if (!new_words) {
+ return NULL;
+ }
+ words = new_words;
+ alloced = wanted;
+ state->words = words;
+ state->alloced = alloced;
+ }
+
+ /* Skip leading spaces. */
+ while (g_ascii_isspace(*p))
+ p++;
+ if (!*p)
+ break;
+
+ /* Add found word to word list. */
+ words[counted++] = p;
+
+ /* Find end of the word. Terminate loop upon EOS. */
+ while (*p && !g_ascii_isspace(*p))
+ p++;
+ if (!*p)
+ break;
+
+ /* More text follows. Terminate the word. */
+ *p++ = '\0';
+ }
+
+ /*
+ * NULL terminate the word list. Provide its length so that
+ * calling code need not re-iterate the list to get the count.
+ */
+ words[counted] = NULL;
+ if (count)
+ *count = counted;
+ state->in_use = TRUE;
+
+ return words;
+}
+
+static void free_text_split(struct context *inc, char **words)
+{
+ struct split_state *state;
+
+ state = &inc->split;
+
+ if (words && words != state->words) {
+ sr_dbg("coding error, free() arg differs from split() result.");
+ }
+
+ /* "Double free" finally releases the memory. */
+ if (!state->in_use) {
+ g_free(state->words);
+ state->words = NULL;
+ state->alloced = 0;
+ }
+
+ /* Mark as no longer in use. */
+ state->in_use = FALSE;
+}
+
+static gboolean have_header(GString *buf)
+{
+ static const char *enddef_txt = "$enddefinitions";
+ static const char *end_txt = "$end";
+
+ char *p, *p_stop;
+
+ /* Search for "end of definitions" section keyword. */
+ p = g_strstr_len(buf->str, buf->len, enddef_txt);
+ if (!p)
+ return FALSE;
+ p += strlen(enddef_txt);
+
+ /* Search for end of section (content expected to be empty). */
+ p_stop = &buf->str[buf->len];
+ p_stop -= strlen(end_txt);
+ while (p < p_stop && g_ascii_isspace(*p))
+ p++;
+ if (strncmp(p, end_txt, strlen(end_txt)) != 0)
+ return FALSE;
+ p += strlen(end_txt);
+
+ return TRUE;
+}
+
+static int parse_timescale(struct context *inc, char *contents)
+{
+ uint64_t p, q;
+
+ /*
+ * The standard allows for values 1, 10 or 100
+ * and units s, ms, us, ns, ps and fs.
+ */
+ if (sr_parse_period(contents, &p, &q) != SR_OK) {
+ sr_err("Parsing $timescale failed.");
+ return SR_ERR_DATA;
+ }
+
+ inc->samplerate = q / p;
+ sr_dbg("Samplerate: %" PRIu64, inc->samplerate);
+ if (q % p != 0) {
+ /* Does not happen unless time value is non-standard */
+ sr_warn("Inexact rounding of samplerate, %" PRIu64 " / %" PRIu64 " to %" PRIu64 " Hz.",
+ q, p, inc->samplerate);
+ }
+
+ return SR_OK;
+}
+
+/*
+ * Handle '$scope' and '$upscope' sections in the input file. Assume that
+ * input signals have a "base name", which may be ambiguous within the
+ * file. These names get declared within potentially nested scopes, which
+ * this implementation uses to create longer but hopefully unique and
+ * thus more usable sigrok channel names.
+ *
+ * Track the currently effective scopes in a string variable to simplify
+ * the channel name creation. Start from an empty string, then append the
+ * scope name and a separator when a new scope opens, and remove the last
+ * scope name when a scope closes. This allows to simply prefix basenames
+ * with the current scope to get a full name.
+ *
+ * It's an implementation detail to keep the trailing NUL here in the
+ * GString member, to simplify the g_strconcat() call in the channel name
+ * creation.
+ *
+ * TODO
+ * - Check whether scope types must get supported, this implementation
+ * does not distinguish between 'module' and 'begin' and what else
+ * may be seen. The first word simply gets ignored.
+ * - Check the allowed alphabet for scope names. This implementation
+ * assumes "programming language identifier" style (alphanumeric with
+ * underscores, plus brackets since we've seen them in example files).
+ */
+static int parse_scope(struct context *inc, char *contents, gboolean is_up)
+{
+ char *sep_pos, *name_pos;
+ char **parts;
+ size_t length;
+
+ /*
+ * The 'upscope' case, drop one scope level (if available). Accept
+ * excess 'upscope' calls, assume that a previous 'scope' section
+ * was ignored because it referenced our software package's name.
+ */
+ if (is_up) {
+ /*
+ * Check for a second right-most separator (and position
+ * right behind that, which is the start of the last
+ * scope component), or fallback to the start of string.
+ * g_string_erase() from that positon to the end to drop
+ * the last component.
+ */
+ name_pos = inc->scope_prefix->str;
+ do {
+ sep_pos = strrchr(name_pos, SCOPE_SEP);
+ if (!sep_pos)
+ break;
+ *sep_pos = '\0';
+ sep_pos = strrchr(name_pos, SCOPE_SEP);
+ if (!sep_pos)
+ break;
+ name_pos = ++sep_pos;
+ } while (0);
+ length = name_pos - inc->scope_prefix->str;
+ g_string_truncate(inc->scope_prefix, length);
+ g_string_append_c(inc->scope_prefix, '\0');
+ sr_dbg("$upscope, prefix now: \"%s\"", inc->scope_prefix->str);
+ return SR_OK;
+ }
+
+ /*
+ * The 'scope' case, add another scope level. But skip our own
+ * package name, assuming that this is an artificial node which
+ * was emitted by libsigrok's VCD output module.
+ */
+ sr_spew("$scope, got: \"%s\"", contents);
+ parts = g_strsplit_set(contents, " \r\n\t", 0);
+ remove_empty_parts(parts);
+ length = g_strv_length(parts);
+ if (length != 2) {
+ sr_err("Unsupported 'scope' syntax: %s", contents);
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+ name_pos = parts[1];
+ if (strcmp(name_pos, PACKAGE_NAME) == 0) {
+ sr_info("Skipping scope with application's package name: %s",
+ name_pos);
+ *name_pos = '\0';
+ }
+ if (*name_pos) {
+ /* Drop NUL, append scope name and separator, and re-add NUL. */
+ g_string_truncate(inc->scope_prefix, inc->scope_prefix->len - 1);
+ g_string_append_printf(inc->scope_prefix,
+ "%s%c%c", name_pos, SCOPE_SEP, '\0');
+ }
+ g_strfreev(parts);
+ sr_dbg("$scope, prefix now: \"%s\"", inc->scope_prefix->str);
+
+ return SR_OK;
+}
+
+/**
+ * Parse a $var section which describes a VCD signal ("variable").
+ *
+ * @param[in] inc Input module context.
+ * @param[in] contents Input text, content of $var section.
+ */
+static int parse_header_var(struct context *inc, char *contents)
+{
+ char **parts;
+ size_t length;
+ char *type, *size_txt, *id, *ref, *idx;
+ gboolean is_reg, is_wire, is_real, is_int;
+ enum sr_channeltype ch_type;
+ size_t size, next_size;
+ struct vcd_channel *vcd_ch;
+
+ /*
+ * Format of $var or $reg header specs:
+ * $var type size identifier reference [opt-index] $end
+ */
+ parts = g_strsplit_set(contents, " \r\n\t", 0);
+ remove_empty_parts(parts);
+ length = g_strv_length(parts);
+ if (length != 4 && length != 5) {
+ sr_warn("$var section should have 4 or 5 items");
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+
+ type = parts[0];
+ size_txt = parts[1];
+ id = parts[2];
+ ref = parts[3];
+ idx = parts[4];
+ if (idx && !*idx)
+ idx = NULL;
+ is_reg = g_strcmp0(type, "reg") == 0;
+ is_wire = g_strcmp0(type, "wire") == 0;
+ is_real = g_strcmp0(type, "real") == 0;
+ is_int = g_strcmp0(type, "integer") == 0;
+
+ if (is_reg || is_wire) {
+ ch_type = SR_CHANNEL_LOGIC;
+ } else if (is_real || is_int) {
+ ch_type = SR_CHANNEL_ANALOG;
+ } else {
+ sr_info("Unsupported signal type: '%s'", type);
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+
+ size = strtol(size_txt, NULL, 10);
+ if (ch_type == SR_CHANNEL_ANALOG) {
+ if (is_real && size != 32 && size != 64) {
+ /*
+ * The VCD input module does not depend on the
+ * specific width of the floating point value.
+ * This is just for information. Upon value
+ * changes, a mere string gets converted to
+ * float, so we may not care at all.
+ *
+ * Strictly speaking we might warn for 64bit
+ * (double precision) declarations, because
+ * sigrok internally uses single precision
+ * (32bit) only.
+ */
+ sr_info("Unexpected real width: '%s'", size_txt);
+ }
+ /* Simplify code paths below, by assuming size 1. */
+ size = 1;
+ }
+ if (!size) {
+ sr_warn("Unsupported signal size: '%s'", size_txt);
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+ if (inc->conv_bits.max_bits < size)
+ inc->conv_bits.max_bits = size;
+ next_size = inc->logic_count + inc->analog_count + size;
+ if (inc->options.maxchannels && next_size > inc->options.maxchannels) {
+ sr_warn("Skipping '%s%s', exceeds requested channel count %zu.",
+ ref, idx ? idx : "", inc->options.maxchannels);
+ inc->ignored_signals = g_slist_append(inc->ignored_signals,
+ g_strdup(id));
+ g_strfreev(parts);
+ return SR_OK;
+ }
+
+ vcd_ch = g_malloc0(sizeof(*vcd_ch));
+ vcd_ch->identifier = g_strdup(id);
+ vcd_ch->name = g_strconcat(inc->scope_prefix->str, ref, idx, NULL);
+ vcd_ch->size = size;
+ vcd_ch->type = ch_type;
+ switch (ch_type) {
+ case SR_CHANNEL_LOGIC:
+ vcd_ch->array_index = inc->logic_count;
+ vcd_ch->byte_idx = vcd_ch->array_index / 8;
+ vcd_ch->bit_mask = 1 << (vcd_ch->array_index % 8);
+ inc->logic_count += size;
+ break;
+ case SR_CHANNEL_ANALOG:
+ vcd_ch->array_index = inc->analog_count++;
+ /* TODO: Use proper 'digits' value for this input module. */
+ vcd_ch->submit_digits = is_real ? 2 : 0;
+ break;
+ }
+ inc->vcdsignals++;
+ sr_spew("VCD signal %zu '%s' ID '%s' (size %zu), sr type %s, idx %zu.",
+ inc->vcdsignals, vcd_ch->name,
+ vcd_ch->identifier, vcd_ch->size,
+ vcd_ch->type == SR_CHANNEL_ANALOG ? "A" : "L",
+ vcd_ch->array_index);
+ inc->channels = g_slist_append(inc->channels, vcd_ch);
+ g_strfreev(parts);
+
+ return SR_OK;
+}
+
+/**
+ * Construct the name of the nth sigrok channel for a VCD signal.
+ *
+ * Uses the VCD signal name for scalar types and single-bit signals.
+ * Uses "signal.idx" for multi-bit VCD signals without a range spec in
+ * their declaration. Uses "signal[idx]" when a range is known and was
+ * verified.
+ *
+ * @param[in] vcd_ch The VCD signal's description.
+ * @param[in] idx The sigrok channel's index within the VCD signal's group.
+ *
+ * @return An allocated text buffer which callers need to release, #NULL
+ * upon failure to create a sigrok channel name.
+ */
+static char *get_channel_name(struct vcd_channel *vcd_ch, size_t idx)
+{
+ char *open_pos, *close_pos, *check_pos, *endptr;
+ gboolean has_brackets, has_range;
+ size_t upper, lower, tmp;
+ char *ch_name;
+
+ /* Handle simple scalar types, and single-bit logic first. */
+ if (vcd_ch->size <= 1)
+ return g_strdup(vcd_ch->name);
+
+ /*
+ * If not done before: Search for a matching pair of brackets in
+ * the right-most position at the very end of the string. Get the
+ * two colon separated numbers between the brackets, which are
+ * the range limits for array indices into the multi-bit signal.
+ * Grab the "base name" of the VCD signal.
+ *
+ * Notice that arrays can get nested. Earlier path components can
+ * be indexed as well, that's why we need the right-most range.
+ * This implementation does not handle bit vectors of size 1 here
+ * by explicit logic. The check for a [0:0] range would even fail.
+ * But the case of size 1 is handled above, and "happens to" give
+ * the expected result (just the VCD signal name).
+ *
+ * This implementation also deals with range limits in the reverse
+ * order, as well as ranges which are not 0-based (like "[4:7]").
+ */
+ if (!vcd_ch->base_name) {
+ has_range = TRUE;
+ open_pos = strrchr(vcd_ch->name, '[');
+ close_pos = strrchr(vcd_ch->name, ']');
+ if (close_pos && close_pos[1])
+ close_pos = NULL;
+ has_brackets = open_pos && close_pos && close_pos > open_pos;
+ if (!has_brackets)
+ has_range = FALSE;
+ if (has_range) {
+ check_pos = &open_pos[1];
+ endptr = NULL;
+ upper = strtoul(check_pos, &endptr, 10);
+ if (!endptr || *endptr != ':')
+ has_range = FALSE;
+ }
+ if (has_range) {
+ check_pos = &endptr[1];
+ endptr = NULL;
+ lower = strtoul(check_pos, &endptr, 10);
+ if (!endptr || endptr != close_pos)
+ has_range = FALSE;
+ }
+ if (has_range && lower > upper) {
+ tmp = lower;
+ lower = upper;
+ upper = tmp;
+ }
+ if (has_range) {
+ if (lower >= upper)
+ has_range = FALSE;
+ if (upper + 1 - lower != vcd_ch->size)
+ has_range = FALSE;
+ }
+ if (has_range) {
+ /* Temporarily patch the VCD channel's name. */
+ *open_pos = '\0';
+ vcd_ch->base_name = g_strdup(vcd_ch->name);
+ *open_pos = '[';
+ vcd_ch->range_lower = lower;
+ vcd_ch->range_upper = upper;
+ }
+ }
+ has_range = vcd_ch->range_lower + vcd_ch->range_upper;
+ if (has_range && idx >= vcd_ch->size)
+ has_range = FALSE;
+ if (!has_range)
+ return g_strdup_printf("%s.%zu", vcd_ch->name, idx);
+
+ /*
+ * Create a sigrok channel name with just the bit's index in
+ * brackets. This avoids "name[7:0].3" results, instead results
+ * in "name[3]".
+ */
+ ch_name = g_strdup_printf("%s[%zu]",
+ vcd_ch->base_name, vcd_ch->range_lower + idx);
+ return ch_name;
+}
+