+ return TRUE;
+}
+
+/* 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;
+ gboolean is_real, is_multibit, is_singlebit, is_string;
+ uint64_t timestamp;
+ char *identifier, *endptr;
+ 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;
+ }
+
+ /*
+ * Numbers prefixed by '#' are timestamps, which translate
+ * to sigrok sample numbers. Apply optional downsampling,
+ * and apply the 'skip' logic. Check the recent timestamp
+ * for plausibility. Submit the corresponding number of
+ * samples of previously accumulated data values to the
+ * session feed.
+ */
+ is_timestamp = curr_first == '#' && g_ascii_isdigit(curr_word[1]);
+ if (is_timestamp) {
+ endptr = NULL;
+ timestamp = strtoull(&curr_word[1], &endptr, 10);
+ if (!endptr || *endptr) {
+ sr_err("Invalid timestamp: %s.", curr_word);
+ ret = SR_ERR_DATA;
+ break;
+ }
+ sr_spew("Got timestamp: %" PRIu64, timestamp);
+ ret = ts_stats_check(&inc->ts_stats, timestamp);
+ if (ret != SR_OK)
+ break;
+ if (inc->options.downsample > 1) {
+ timestamp /= inc->options.downsample;
+ sr_spew("Downsampled timestamp: %" PRIu64, timestamp);
+ }