+ 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).
+ * Uses DIY logic to scan for the literals' presence including
+ * empty space between keywords. MUST NOT modify the caller's
+ * input data, potentially executes several times on the same
+ * receive buffer, and executes outside of the processing the
+ * file's data section.
+ */
+ 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, *type_pos;
+ 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);
+ type_pos = sr_text_next_word(contents, &contents);
+ if (!type_pos) {
+ sr_err("Cannot parse 'scope' directive");
+ return SR_ERR_DATA;
+ }
+ name_pos = sr_text_next_word(contents, &contents);
+ if (!name_pos || contents) {
+ sr_err("Cannot parse 'scope' directive");
+ return SR_ERR_DATA;
+ }
+
+ 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');
+ }
+ 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 *type, *size_txt, *id, *ref, *idx;
+ gboolean is_reg, is_wire, is_real, is_int;
+ gboolean is_str;
+ 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
+ */
+ type = sr_text_next_word(contents, &contents);
+ size_txt = sr_text_next_word(contents, &contents);
+ id = sr_text_next_word(contents, &contents);
+ ref = sr_text_next_word(contents, &contents);
+ idx = sr_text_next_word(contents, &contents);
+ if (idx && !*idx)
+ idx = NULL;
+ if (!type || !size_txt || !id || !ref || contents) {
+ sr_warn("$var section should have 4 or 5 items");
+ return SR_ERR_DATA;
+ }
+
+ 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;
+ is_str = g_strcmp0(type, "string") == 0;
+
+ if (is_reg || is_wire) {
+ ch_type = SR_CHANNEL_LOGIC;
+ } else if (is_real || is_int) {
+ ch_type = SR_CHANNEL_ANALOG;
+ } else if (is_str) {
+ sr_warn("Skipping id %s, name '%s%s', unsupported type '%s'.",
+ id, ref, idx ? idx : "", type);
+ inc->ignored_signals = g_slist_append(inc->ignored_signals,
+ g_strdup(id));
+ return SR_OK;
+ } else {
+ sr_err("Unsupported signal type: '%s'", type);
+ 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);
+ 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));
+ 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);
+
+ return SR_OK;