I need to clarify a couple of things.
fun
u8{i:uint8}
(i: int(i)): uint8(i) = cast{uint8(i)}(i)
I interpret this to mean:
for all i:uint8 I can pass in an int that is restricted to a value 0-255 and it returns a uint8 with the same 0-255 restriction.
Pretty nice actually.
But, when I call like this:
val value = 2: uint8
val _ = to_digit(u8_(value >> 4))
It can't seem to check that a shifted value is within the constraint.
That just means to me the constraint solver does not handle shifts. So as the caller to to_digit, I would need to certify that the value meets the requirement with some kind of cast. Is there a general form of cast that can include something like cast{i:uint8 | i < 16}(value >> 4)?
As an alternative, I suppose I could create a function that does the conversion, with either a run time check that aborts, or with some range clamping, etc. But if I do that, all I did was move the original clamping, the side effect of a pattern match on anything that returns 0, to another place in the code.
I know there is no ultimate escape if bytes are given the program via USB and they must be interpreted as hex, it is more a matter of where they are checked, clamped, or cause an error to be returned by USB. But I would like to know how you would handle this. Would you make a function to get from byte to constrained hex byte, use a cast, etc.