Wobblechar – parse ASCII/UTF-8 waveform diagrams into decoded samples (no_std-compatible)

Coming from embedded C/C++, I wanted a less tedious way to write tests on electronic signals. I saw myself write ascii-waveforms in the docs and thought: Why not use this as input. So I completed this project as a Rust learning project.

Instead of manually constructing test vectors, I wanted to write this:

CLK: _|‾|_|‾|_
DAT: ___‾‾‾___

To then get an iterator of timestep items with decoded values. I also included an index and a changed flag to easily filter the result.

As I am working on embedded, I had to make it completely no_std compatible. If you use it in std-context, I added support for hashmap character-mappers as well.

I think this crate is very useful for testing signal processing logic, protocol decoders, or anything that operates on input signal lines. It supports custom character mappings and labeled multi-line blocks for interleaved signals. Curious if anyone sees other use cases — I was thinking about things like encoding bit patterns for EEPROM initialization. Drop me a line!

10 Likes

A comment on the code: In the tests in src/parser.rs, is there a specific reason for CASE_3_2, EXP_3_2 variables that are used only once[1], and extracting from Item<T, N> to RecMulti etc. types, when the N is (mostly) the same for a test function and Item's fields are all public?


  1. Nothing really wrong, but those numbers can get hard to manage manually (especially if you want to insert/reorder); if you write the values inline you can avoid numbering ↩︎

I can see that, given a very careful formatting, I could have indeed written it without the named consts. I will probably take your advise and change this shortly.
As for the map(): this way I can convert to the same kind of thing and assert_equal for the whole thing. Easier to understand fails. At least I think so :wink:

1 Like

I updated my tests as you suggested. Thanks for the heads up! Cheers. mous

This is perhaps a little over the top, but you could imagine generalizing this. _, |, and ‾ are just symbols representing states; perhaps you could allow a set of states to be defined, e.g. R and Ω might represent states, or events causing transitions, in some state machine. Such a facility might be useful in specifying the expected behavior of a state machine under test, or of an interacting set of state machines. Just a harebrained thought. :slight_smile:

EDIT: Actually, it wouldn't have to be a state machine at all. For (a silly) example, perhaps the text

A: 0123456789
B: 0120120120

could specify the results of a test of A mod 3.

I guess the general point is that it can sometimes be convenient to represent test instances as single columns of letters across a set of rows.

1 Like

Hi Amigo.
Not sure I understand what you mean, but yeah, I guess what you want would be possible. Via number-mapper, Wobblechar can actually map any character to any number or even (custom mapper) to any other type. The only thing is that it must be just one character. So your mod 3 would be ok.
But ok, Wobblechar could be seen as a special case of a broader thing, perhaps.. but are we not then writing a general parser? Indeed a bit out of scope. But let me know if I can be of help!
Best, mous

1 Like

But that's not what you're doing now, you're comparing individual fields with the answer & iteration order. Is that a todo?

By "whole thing" I meant per item — one assert_eq! for the entire RecMulti struct at once, rather than comparing individual fields separately. Not a todo :slight_smile:
But anyway, this all seems not really an issue, or do you propose a real change?

1 Like

I took a closer look and it seems it's my oversight. By putting Item's changed and values in a separate struct, you can compare them at once, and give them a different failure message than the index, and avoid typing Item::index into the test answers. :slight_smile:

What I was thinking is to use Items as answers and compare them directly, but then you can't give a dedicated failure message for changed and values and let the L&R sides' debug repr not contain index, and it'll take something like a macro or dedicated function to avoid typing each index. (Or they can be initialized to 0 and have that field replaced with enumerate's index at runtime)

1 Like

Thanks for taking the time to look at the code. And I think we agree that this is then the way to go. :+1:

1 Like