+ found = FALSE;
+ for (l = inc->channels; l; l = l->next) {
+ vcd_ch = l->data;
+ if (vcd_ch->type != SR_CHANNEL_ANALOG)
+ continue;
+ if (g_strcmp0(identifier, vcd_ch->identifier) != 0)
+ continue;
+
+ /* Found our (analog) channel. */
+ found = TRUE;
+ sr_spew("Processing real data, id '%s', ch %zu, val %.16g",
+ identifier, vcd_ch->array_index, real_val);
+ inc->current_floats[vcd_ch->array_index] = real_val;
+ }
+ if (!found && !is_ignored(inc, identifier))
+ sr_warn("VCD signal not found for ID '%s'.", identifier);
+}
+
+/*
+ * Converts a bit position's text character to a number value.
+ *
+ * TODO Check for complete coverage of Verilog's standard logic values
+ * (IEEE-1364). The set is said to be “01XZHUWL-”, which only a part of
+ * is handled here. What would be the complete mapping?
+ * - 0/L -> bit value 0
+ * - 1/H -> bit value 1
+ * - X "don't care" -> TODO
+ * - Z "high impedance" -> TODO
+ * - W "weak(?)" -> TODO
+ * - U "undefined" -> TODO
+ * - '-' "TODO" -> TODO
+ *
+ * For simplicity, this input module implementation maps "known low"
+ * values to 0, and "known high" values to 1. All other values will
+ * end up assuming "low" (return number 0), while callers might warn.
+ * It's up to users to provide compatible input data, or accept the
+ * warnings. Silently accepting unknown input data is not desirable.
+ */
+static uint8_t vcd_char_to_value(char bit_char, int *warn)
+{
+
+ bit_char = g_ascii_tolower(bit_char);
+
+ /* Convert the "undisputed" variants. */
+ if (bit_char == '0' || bit_char == 'l')
+ return 0;
+ if (bit_char == '1' || bit_char == 'h')
+ return 1;
+
+ /* Convert the "uncertain" variants. */
+ if (warn)
+ *warn = 1;
+ if (bit_char == 'x' || bit_char == 'z')
+ return 0;
+ if (bit_char == 'u')
+ return 0;
+
+ /* Unhandled input text. */
+ return ~0;
+}
+
+/* Parse one text line of the data section. */
+static int parse_textline(const struct sr_input *in, char *lines)
+{
+ struct context *inc;
+ int ret;
+ char **words;
+ size_t word_count, word_idx;
+ char *curr_word, *next_word, curr_first;
+ gboolean is_timestamp, is_section, is_real, is_multibit, is_singlebit;
+ uint64_t timestamp;
+ char *identifier;
+ size_t count;
+
+ inc = in->priv;
+
+ /*
+ * Split the caller's text lines into a list of space separated
+ * words. Note that some of the branches consume the very next
+ * words as well, and assume that both adjacent words will be
+ * available when the first word is seen. This constraint applies
+ * to bit vector data, multi-bit integers and real (float) data,
+ * as well as single-bit data with whitespace before its
+ * identifier (if that's valid in VCD, we'd accept it here).
+ * The fact that callers always pass complete text lines should
+ * make this assumption acceptable.
+ */
+ ret = SR_OK;
+ words = split_text_line(inc, lines, &word_count);
+ for (word_idx = 0; word_idx < word_count; word_idx++) {
+ /*
+ * Make the next two words available, to simpilify code
+ * paths below. The second word is optional here.
+ */
+ curr_word = words[word_idx];
+ if (!curr_word && !curr_word[0])
+ continue;
+ curr_first = g_ascii_tolower(curr_word[0]);
+ next_word = words[word_idx + 1];
+ if (next_word && !next_word[0])
+ next_word = NULL;
+
+ /*
+ * Optionally skip some sections that can be interleaved
+ * with data (and may or may not be supported by this
+ * input module). If the section is not skipped but the
+ * $end keyword needs to get tracked, specifically handle
+ * this case, for improved robustness (still reject files
+ * which happen to use invalid syntax).
+ */
+ if (inc->skip_until_end) {
+ if (strcmp(curr_word, "$end") == 0) {
+ /* Done with unhandled/unknown section. */
+ sr_dbg("done skipping until $end");
+ inc->skip_until_end = FALSE;
+ } else {
+ sr_spew("skipping word: %s", curr_word);
+ }
+ continue;
+ }
+ if (inc->ignore_end_keyword) {
+ if (strcmp(curr_word, "$end") == 0) {
+ sr_dbg("done ignoring $end keyword");
+ inc->ignore_end_keyword = FALSE;
+ continue;
+ }
+ }
+
+ /*
+ * There may be $keyword sections inside the data part of
+ * the input file. Do inspect some of the sections' content
+ * but ignore their surrounding keywords. Silently skip
+ * unsupported section types (which transparently covers
+ * $comment sections).
+ */
+ is_section = curr_first == '$' && curr_word[1];
+ if (is_section) {
+ gboolean inspect_data;
+
+ inspect_data = FALSE;
+ inspect_data |= g_strcmp0(curr_word, "$dumpvars") == 0;
+ inspect_data |= g_strcmp0(curr_word, "$dumpon") == 0;
+ inspect_data |= g_strcmp0(curr_word, "$dumpoff") == 0;
+ if (inspect_data) {
+ /* Ignore keywords, yet parse contents. */
+ sr_dbg("%s section, will parse content", curr_word);
+ inc->ignore_end_keyword = TRUE;
+ } else {
+ /* Ignore section from here up to $end. */
+ sr_dbg("%s section, will skip until $end", curr_word);
+ inc->skip_until_end = TRUE;
+ }
+ continue;
+ }